执行计划中最拖后腿的是type=all(全表扫描)、rows远大于实际返回行数、extra含using filesort或using temporary;三者任一出现即表明性能瓶颈。
怎么看执行计划里哪一步最拖后腿
直接在navicat查询编辑器中右键选中sql → 点“解释”,弹出的表格里重点盯住三列:type、rows、extra。type是all就说明在全表扫描;rows值比实际返回结果大几倍甚至几十倍,说明mysql预估要扫太多行;extra里出现using filesort或using temporary,基本等于告诉你要重写——尤其是using filesort出现在order by字段没索引时,排序过程完全在内存/磁盘里硬扛。
哪些SQL写法一改就见效
很多慢查询不是数据量大,而是写法把索引废了。常见可立即修正的点:
-
WHERE YEAR(create_time) = 2023→ 改成WHERE create_time >= '2023-01-01' AND create_time (函数包裹索引字段必失效) -
WHERE status IN ('active', 'pending') AND user_id = 123→ 如果已有索引INDEX(user_id, status),但执行计划里key为空,试试把条件顺序调换:WHERE user_id = 123 AND status IN ('active', 'pending')(复合索引最左前缀必须严格匹配) -
SELECT * FROM orders WHERE deleted = 0 ORDER BY created_at DESC LIMIT 20→ 若deleted区分度极低(比如95%都是0),优化器可能放弃走created_at索引,改用WHERE deleted = 0 AND created_at > '2025-01-01' ORDER BY created_at DESC LIMIT 20加时间范围过滤
JOIN和子查询怎么选才不卡
执行计划里select_type是SUBQUERY或DERIVED,且对应rows特别高,大概率该换写法了:
- 用
EXISTS代替IN (SELECT ...):比如SELECT id FROM users WHERE EXISTS (SELECT 1 FROM orders WHERE orders.user_id = users.id AND status = 'paid'),避免子查询先生成中间结果集 - 大表JOIN小表时,确保
ON字段两边类型一致:比如users.id是BIGINT,但orders.user_id是VARCHAR,隐式转换会让索引失效 - 避免
LEFT JOIN后在右表字段上写WHERE条件(如LEFT JOIN logs ON u.id = logs.user_id WHERE logs.level = 'error'),这实际变成INNER JOIN,还可能让左表索引失效;应把条件移到ON里:LEFT JOIN logs ON u.id = logs.user_id AND logs.level = 'error'
为什么改完还是没走索引
执行计划里possible_keys有值但key为空,不代表SQL写错了,可能是这些隐形原因:
- 表统计信息过期:
ANALYZE TABLE table_name强制刷新,尤其在大批量导入/删除后 - 索引选择性太差:比如
gender只有'男'/'女'两个值,即使建了索引,优化器也倾向全表扫描;这类字段单独建索引意义不大 - 数据分布倾斜:某值占99%,优化器认为走索引反而更慢,可加
FORCE INDEX临时验证,但长期要靠业务逻辑拆分或覆盖索引解决 -
WHERE条件含OR且两边字段无联合索引:比如WHERE a = 1 OR b = 2,即使a和b各自有索引,也可能全表扫描;考虑改写为UNION或补一个INDEX(a,b)
真正卡住的地方往往不在SQL本身,而在执行计划暴露的索引结构与查询模式之间的错位——盯着key和rows的落差,比反复重写语句更有效。











