Skip to content

8 种专坑同事的 SQL 写法,性能降低 100 倍!

发布:芋道源码 时间:2026 年 2 月 13 日 来源:yq.aliyun.com/articles/72501

在数据库开发中,一些看似正常的 SQL 写法,实则会导致性能大幅下降,甚至拖垮整个系统。本文整理了 8 种典型的低效 SQL 写法,结合实战案例分析问题根源,并给出对应的优化方案,同时讲解 SQL 执行顺序与调优核心思路,帮你避开这些坑。

先搞懂:SQL 的书写与执行顺序

书写顺序

1 SELECT [DISTINCT] (select_field)
2 FROM (table)
3 (join_type) JOIN (table)
4 ON (join_condition)
5 WHERE (where_condition)
6 GROUP BY (group_by_field)
7 HAVING (having_condition)
8 ORDER BY (order_by_field)
9 LIMIT (limit_number)

实际执行顺序

SQL
FROM <left_table>
ON <join_condition>
<join_type> JOIN <right_table>
WHERE <where_condition>
GROUP BY <group_by_list>
HAVING <having_condition>
SELECT DISTINCT <select_list>
ORDER BY <order_by_condition>
LIMIT <limit_number>

核心:SQL 执行并非按书写顺序,而是先从表关联、条件过滤,再做聚合、排序、分页,理解这一点是调优的基础。

一、LIMIT 语句:大偏移量分页性能暴跌

问题场景

分页查询是高频场景,但大偏移量的LIMIT会让数据库从头扫描数据,即使有索引也无法避免,性能随偏移量增大急剧下降。

SQL
SELECT *
FROM  operation
WHERE  type = 'SQLStats'
       AND name = 'SlowLog'
ORDER  BY create_time
LIMIT  1000000, 10; -- 偏移量100万,仅查10条仍极慢

问题根源

数据库无法直接定位到第 100 万条记录,即使有索引,也需要从头遍历计算,偏移量越大,耗时越长。

优化方案

利用上一页的最大值作为查询条件,结合索引实现精准定位,查询时间基本固定,不随数据量增长变化。

SQL
SELECT  *
FROM    operation
WHERE   type = 'SQLStats'
AND     name = 'SlowLog'
AND     create_time > '2017-03-16 14:00:00' -- 上一页最后一条的create_time
ORDER BY create_time limit 10;

适用场景

前端翻页、大数据分批导出等可传递上一页边界值的场景。

二、隐式转换:导致索引失效

问题场景

查询变量与字段定义类型不匹配,MySQL 会对表字段做隐式类型转换,函数作用于字段后,索引直接失效。

SQL
mysql> explain extended
SELECT *
FROM  my_balance b
WHERE  b.bpn = 14000000123 -- bpn是varchar(20)类型,传入数字
       AND b.isverified IS NULL ;

执行结果提示索引失效:

| Warning | 1739 | Cannot use ref access on index 'bpn' due to type or collation conversion on field 'bpn'

问题根源

MySQL 会将字符串字段转换为数字再进行比较,相当于对bpn字段执行了CAST(bpn AS UNSIGNED)函数,索引无法被利用,触发全表扫描。

优化方案

保证查询条件与字段类型严格一致,字符串类型字段传入字符串参数,避免隐式转换。

SQL
SELECT *
FROM  my_balance b
WHERE  b.bpn = '14000000123' -- 传入字符串
       AND b.isverified IS NULL ;

三、关联更新/删除:嵌套子查询效率极低

问题场景

MySQL5.6+虽支持物化特性,但仅针对查询语句,更新/删除使用嵌套子查询会触发循环/嵌套子查询(DEPENDENT SUBQUERY),执行效率极差。

SQL
UPDATE operation o
SET    status = 'applying'
WHERE  o.id IN (SELECT id
                FROM  (SELECT o.id,
                              o.status
                       FROM  operation o
                       WHERE  o.group = 123
                              AND o.status NOT IN ( 'done' )
                       ORDER  BY o.parent,
                                o.id
                       LIMIT  1) t);

执行计划问题

子查询为DEPENDENT SUBQUERY,循环执行,原语句执行耗时7 秒

优化方案

将嵌套子查询重写为 JOIN 方式,子查询变为DERIVED(派生表),执行效率大幅提升。

SQL
UPDATE operation o
       JOIN (SELECT o.id,
                    o.status
             FROM  operation o
             WHERE  o.group = 123
                    AND o.status NOT IN ( 'done' )
             ORDER  BY o.parent,
                      o.id
             LIMIT  1) t
       ON o.id = t.id
SET    status = 'applying'

优化效果

执行时间从 7 秒降至2 毫秒,执行计划大幅简化。

四、混合排序:MySQL 无法利用索引的混合排序

问题场景

MySQL 无法为混合排序(ASC+DESC) 利用索引,导致全表扫描+文件排序,性能低下。

SQL
SELECT *
FROM   my_order o
       INNER JOIN my_appraise a ON a.orderid = o.id
ORDER  BY a.is_reply ASC,
          a.appraise_time DESC
LIMIT  0, 20

执行计划问题

全表扫描(type=ALL)+文件排序(Using filesort),执行耗时1.58 秒

优化方案

利用排序字段的有限枚举特性(如is_reply只有 0/1 两个值),拆分为多个子查询通过UNION ALL合并,再统一排序。

SQL
SELECT *
FROM  ((SELECT *
        FROM  my_order o
              INNER JOIN my_appraise a
                     ON a.orderid = o.id
                     AND is_reply = 0
        ORDER BY appraise_time DESC
        LIMIT 0, 20)
       UNION ALL
       (SELECT *
        FROM  my_order o
              INNER JOIN my_appraise a
                     ON a.orderid = o.id
                     AND is_reply = 1
        ORDER BY appraise_time DESC
        LIMIT 0, 20)) t
ORDER BY  is_reply ASC,
          appraise_time DESC
LIMIT 20;

优化效果

执行时间降至2 毫秒,充分利用单字段索引。

五、EXISTS 语句:嵌套子查询不如 JOIN 高效

问题场景

MySQL 对EXISTS子句仍采用嵌套子查询执行方式,效率低于直接 JOIN 关联。

SQL
SELECT *
FROM  my_neighbor n
       LEFT JOIN my_neighbor_apply sra
              ON n.id = sra.neighbor_id
                 AND sra.user_id = 'xxx'
WHERE  n.topic_status < 4
       AND EXISTS(SELECT 1
                  FROM  message_info m
                  WHERE  n.id = m.neighbor_id
                         AND m.inuser = 'xxx')
       AND n.topic_type <> 5

执行计划问题

子查询为DEPENDENT SUBQUERY,嵌套执行,耗时1.93 秒

优化方案

去掉 EXISTS,替换为 INNER JOIN 关联,避免嵌套子查询,利用索引实现高效查询。

SQL
SELECT *
FROM  my_neighbor n
       INNER JOIN message_info m
              ON n.id = m.neighbor_id
                 AND m.inuser = 'xxx'
       LEFT JOIN my_neighbor_apply sra
              ON n.id = sra.neighbor_id
                 AND sra.user_id = 'xxx'
WHERE  n.topic_status < 4
       AND n.topic_type <> 5

优化效果

执行时间降至1 毫秒,执行计划变为简单的表关联,无嵌套子查询。

六、条件下推:外部条件未下推到子查询/视图

问题场景

外部查询条件无法下推到聚合、LIMIT、UNION、字段子查询等复杂子查询,导致子查询全表计算后再过滤,性能低下。

SQL
SELECT *
FROM  (SELECT target,
              Count(*)
       FROM  operation
       GROUP  BY target) t
WHERE  target = 'rm-xxxx'

执行计划问题

子查询先全表聚合(DERIVED),生成临时表后再过滤,相当于“先算所有,再挑一个”。

优化方案

将查询条件直接下推到子查询内部,先过滤再聚合,充分利用索引,减少计算量。

SQL
SELECT target,
       Count(*)
FROM  operation
WHERE  target = 'rm-xxxx'
GROUP  BY target

优化效果

直接利用target字段索引,从“全表聚合”变为“单条过滤聚合”,性能大幅提升。

七、提前缩小范围:先过滤再关联,避免大表全连接

问题场景

先做全表左连接,再过滤、排序、分页,导致连接的数据量极大,触发文件排序和临时表,耗时极长。

SQL
SELECT *
FROM  my_order o
       LEFT JOIN my_userinfo u
              ON o.uid = u.uid
       LEFT JOIN my_productinfo p
              ON o.pid = p.pid
WHERE  ( o.display = 0 )
       AND ( o.ostaus = 1 )
ORDER  BY o.selltime DESC
LIMIT  0, 15

执行计划问题

主表全表扫描(90 万行),先连接再排序,执行耗时12 秒

优化方案

先对主表过滤、排序、分页,缩小数据范围,再进行左连接,仅对少量数据做关联操作。

SQL
SELECT *
FROM (SELECT *
      FROM  my_order o
      WHERE  ( o.display = 0 )
             AND ( o.ostaus = 1 )
      ORDER BY o.selltime DESC
      LIMIT 0, 15) o
      LEFT JOIN my_userinfo u
             ON o.uid = u.uid
      LEFT JOIN my_productinfo p
             ON o.pid = p.pid
ORDER BY  o.selltime DESC
limit 0, 15

优化效果

执行时间降至1 毫秒,先通过 LIMIT 将主表数据缩小到 15 行,再关联其他表,计算量大幅减少。

八、中间结果集下推:避免子查询全表计算

问题场景

左连接的子查询做全表聚合,即使最终只需要少量匹配数据,仍会计算全表,性能低下。

SQL
SELECT    a.*,
          c.allocated
FROM      (
                SELECT  resourceid
                FROM    my_distribute d
                WHERE   isdelete = 0
                        AND     cusmanagercode = '1234567'
                ORDER BY salecode limit 20
          ) a
LEFT JOIN (
                SELECT  resourcesid,sum(ifnull(allocation, 0) * 12345) allocated
                FROM    my_resources
                GROUP BY resourcesid
          ) c
ON        a.resourceid = c.resourcesid

问题根源

子查询c全表聚合my_resources,而最终仅需要与a匹配的 20 条数据,全表计算完全多余,执行耗时2 秒

优化方案

将中间结果集a下推到子查询c,先过滤再聚合,仅对需要的部分数据做计算。

SQL
SELECT    a.*,
          c.allocated
FROM      (
                SELECT  resourceid
                FROM    my_distribute d
                WHERE   isdelete = 0
                        AND     cusmanagercode = '1234567'
                ORDER BY salecode limit 20
          ) a
LEFT JOIN (
                SELECT  resourcesid,sum(ifnull(allocation, 0) * 12345) allocated
                FROM    my_resources r,
                        a
                WHERE   r.resourcesid = a.resourcesid
                GROUP BY resourcesid
          ) c
ON        a.resourceid = c.resourcesid

进阶优化:使用 WITH 语句简化重复子查询

上述优化后子查询a仅定义一次,避免重复计算,让 SQL 更简洁清晰:

SQL
WITH a AS(
        SELECT  resourceid
        FROM    my_distribute d
        WHERE   isdelete = 0
                AND     cusmanagercode = '1234567'
        ORDER BY salecode limit 20
)
SELECT    a.*,
          c.allocated
FROM      a
LEFT JOIN (
                SELECT  resourcesid,sum(ifnull(allocation, 0) * 12345) allocated
                FROM    my_resources r,
                        a
                WHERE   r.resourcesid = a.resourcesid
                GROUP BY resourcesid
          ) c
ON        a.resourceid = c.resourcesid

优化效果

执行时间降至2 毫秒,子查询c从全表聚合变为精准过滤聚合。

总结:SQL 性能调优的核心思路

  1. 理解执行顺序:先过滤、再关联、最后聚合排序,避免“先全量计算,再少量过滤”;
  2. 避免索引失效:杜绝隐式转换、函数作用于字段、模糊查询左通配等行为;
  3. 缩小计算范围:优先通过 WHERE、LIMIT 过滤数据,再做关联、聚合、排序;
  4. 拒绝低效嵌套:将 DEPENDENT SUBQUERY 重写为 JOIN 或 DERIVED 子查询;
  5. 善用语法特性:利用 WITH 语句简化重复子查询,提升 SQL 可读性和执行效率;
  6. 结合业务特性:如有限枚举的排序字段可拆分为 UNION ALL,充分利用单字段索引。

数据库的编译器并非尽善尽美,了解其特性,避开其短处,将算法思想融入 SQL 编写,才能写出高性能的 SQL 语句。同时,简洁清晰的 SQL 不仅便于维护,也能减少数据库的解析和执行负担。


许可协议

本文采用 CCBYNCSA

署名-非商业性使用-相同方式共享 4.0 国际 许可协议,转载请注明出处。

上次更新于: