MySQL EXPLAIN ANALYZE:把慢查询成本量化出来

普通 EXPLAIN 展示优化器的估算,EXPLAIN ANALYZE 会真正执行查询,并给出各执行节点的实际耗时、返回行数和循环次数。两者对比可以发现统计信息失真、关联顺序错误和“看似用了索引却扫描很多”的问题。

先保存完整查询现场

分析前记录 SQL、参数、执行频率、返回行数、慢查询时间和表规模。相同 SQL 使用不同参数时,数据分布可能让执行成本完全不同。不要只把参数替换成一个“方便测试”的 ID。

EXPLAIN FORMAT=TREE
SELECT article_id, title, publish_time
FROM article
WHERE status = '0'
  AND publish_status = 'published'
ORDER BY publish_time DESC
LIMIT 20;

普通 Explain 先看访问方式、候选索引、实际选择索引、估算行数和 Extra。Using filesortUsing temporary 不一定就是错误,但要结合返回规模与频率判断。

再用 EXPLAIN ANALYZE 验证

EXPLAIN ANALYZE
SELECT article_id, title, publish_time
FROM article
WHERE status = '0'
  AND publish_status = 'published'
ORDER BY publish_time DESC
LIMIT 20;

输出中的 actual time 通常包含首行与全部结果耗时,rows 是每次循环实际返回行数,loops 是节点执行次数。真正成本可近似看作每次工作量乘以循环次数。Nested Loop 内层只要每次很快,但循环几十万次,仍可能成为主要瓶颈。

由于它会执行查询,生产环境必须谨慎。优先在只读副本或等量测试数据上运行;对高成本查询设置会话级超时;确认不是会改变数据的语句;避免在业务高峰反复执行。

估算与实际差异告诉了什么

  • 估算 10 行、实际 10 万行:统计信息或数据分布可能失真。
  • 选择了索引但过滤后只剩极少数据:索引顺序可能不匹配查询。
  • 内层节点循环次数巨大:关联顺序或关联键需要检查。
  • 排序节点输入远大于 LIMIT:缺少能同时过滤和排序的索引。
  • 单行查找仍很慢:检查随机 I/O、回表、大字段和锁等待。

更新统计信息可以验证估算问题:

ANALYZE TABLE article;

但不要把它当成永久修复。数据倾斜明显时,需要重新设计索引、查询或数据模型。

索引设计回到查询目标

对上面的查询,(status, publish_status, publish_time) 能连续使用等值过滤并提供排序。若列表只需要少数字段,可考虑覆盖索引,但不要把大文本列塞进索引。每个新索引都会增加写入、Buffer Pool 和维护成本,应通过慢查询频率评估收益。

完整优化闭环

  1. 从慢查询日志选择高总耗时 SQL,而不只是最慢单次。
  2. 用真实参数执行 Explain 与 Analyze。
  3. 比较估算行数、实际行数、循环次数和耗时。
  4. 一次只调整查询、索引或统计信息中的一项。
  5. 再次执行相同测试,并观察线上总耗时与写入成本。

参考资料