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)实际执行顺序
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会让数据库从头扫描数据,即使有索引也无法避免,性能随偏移量增大急剧下降。
SELECT *
FROM operation
WHERE type = 'SQLStats'
AND name = 'SlowLog'
ORDER BY create_time
LIMIT 1000000, 10; -- 偏移量100万,仅查10条仍极慢问题根源
数据库无法直接定位到第 100 万条记录,即使有索引,也需要从头遍历计算,偏移量越大,耗时越长。
优化方案
利用上一页的最大值作为查询条件,结合索引实现精准定位,查询时间基本固定,不随数据量增长变化。
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 会对表字段做隐式类型转换,函数作用于字段后,索引直接失效。
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)函数,索引无法被利用,触发全表扫描。
优化方案
保证查询条件与字段类型严格一致,字符串类型字段传入字符串参数,避免隐式转换。
SELECT *
FROM my_balance b
WHERE b.bpn = '14000000123' -- 传入字符串
AND b.isverified IS NULL ;三、关联更新/删除:嵌套子查询效率极低
问题场景
MySQL5.6+虽支持物化特性,但仅针对查询语句,更新/删除使用嵌套子查询会触发循环/嵌套子查询(DEPENDENT SUBQUERY),执行效率极差。
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(派生表),执行效率大幅提升。
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) 利用索引,导致全表扫描+文件排序,性能低下。
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合并,再统一排序。
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 关联。
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 关联,避免嵌套子查询,利用索引实现高效查询。
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、字段子查询等复杂子查询,导致子查询全表计算后再过滤,性能低下。
SELECT *
FROM (SELECT target,
Count(*)
FROM operation
GROUP BY target) t
WHERE target = 'rm-xxxx'执行计划问题
子查询先全表聚合(DERIVED),生成临时表后再过滤,相当于“先算所有,再挑一个”。
优化方案
将查询条件直接下推到子查询内部,先过滤再聚合,充分利用索引,减少计算量。
SELECT target,
Count(*)
FROM operation
WHERE target = 'rm-xxxx'
GROUP BY target优化效果
直接利用target字段索引,从“全表聚合”变为“单条过滤聚合”,性能大幅提升。
七、提前缩小范围:先过滤再关联,避免大表全连接
问题场景
先做全表左连接,再过滤、排序、分页,导致连接的数据量极大,触发文件排序和临时表,耗时极长。
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 秒。
优化方案
先对主表过滤、排序、分页,缩小数据范围,再进行左连接,仅对少量数据做关联操作。
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 行,再关联其他表,计算量大幅减少。
八、中间结果集下推:避免子查询全表计算
问题场景
左连接的子查询做全表聚合,即使最终只需要少量匹配数据,仍会计算全表,性能低下。
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,先过滤再聚合,仅对需要的部分数据做计算。
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 更简洁清晰:
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 性能调优的核心思路
- 理解执行顺序:先过滤、再关联、最后聚合排序,避免“先全量计算,再少量过滤”;
- 避免索引失效:杜绝隐式转换、函数作用于字段、模糊查询左通配等行为;
- 缩小计算范围:优先通过 WHERE、LIMIT 过滤数据,再做关联、聚合、排序;
- 拒绝低效嵌套:将 DEPENDENT SUBQUERY 重写为 JOIN 或 DERIVED 子查询;
- 善用语法特性:利用 WITH 语句简化重复子查询,提升 SQL 可读性和执行效率;
- 结合业务特性:如有限枚举的排序字段可拆分为 UNION ALL,充分利用单字段索引。
数据库的编译器并非尽善尽美,了解其特性,避开其短处,将算法思想融入 SQL 编写,才能写出高性能的 SQL 语句。同时,简洁清晰的 SQL 不仅便于维护,也能减少数据库的解析和执行负担。
许可协议
