MySQL EXPLAIN:读懂执行计划
一句话结论(30s)
EXPLAIN 读执行计划的本质是看优化器如何用索引和估算代价,因为 type 从 const 到 ALL 逐级变差、直接反映扫描方式的优劣。关键设计是三个信号:key 为空说明没走索引、rows 是估算扫描行数、Extra 里 Using filesort/Using temporary 是性能杀手。权衡在于:优化器基于统计信息选索引可能选错,需要 FORCE INDEX、ANALYZE TABLE、OPTIMIZER_TRACE 三件套兜底。
核心原理(2min)
EXPLAIN 输出里 type 是最重要的列,从 const(主键/唯一索引等值,最多 1 行)→ eq_ref → ref → range → index(全索引扫)→ ALL(全表扫)逐级变差,出现 ALL 要检查是否缺索引。possible_keys 是优化器认为可选的索引,key 是实际用的索引,key 为 NULL 说明索引失效;rows 是估算扫描行数(基于统计信息,非精确值)。Extra 里 Using index(覆盖索引不回表)最优,Using index condition 表示 ICP 下推,Using filesort 表示排序走不了索引、是大数据量表杀手,需检查 ORDER BY 列是否在联合索引中。当优化器选错索引时,可用 FORCE INDEX 强制、ANALYZE TABLE 重采样、OPTIMIZER_TRACE 看完整代价估算。
底层深入(5-10min)
关键字段
EXPLAIN SELECT * FROM orders WHERE user_id = 100 ORDER BY create_time;
输出关键字段:
type(访问类型)——最重要的列
const → 主键/唯一索引等值查询,最多1行
eq_ref → JOIN中主键/唯一索引关联,每行匹配最多1行
ref → 普通索引等值查询
range → 索引范围扫描(>, <, BETWEEN, IN)
index → 全索引扫描(比全表快但仍是全扫)
ALL → 全表扫描 ← 需要优化
从 const 到 ALL 逐级更差。出现 ALL → 检查是否缺索引。
💭 想一想:为什么单看
type这一列就能判断扫描方式优劣?——因为访问类型直接对应”要扫多少行、要不要回表、是不是随机 IO”:const 只碰一行,ALL 却要逐行扫完整表,代价差了几个数量级。看懂type等于看懂优化器做的第一步决策。
key & possible_keys
possible_keys:优化器认为”可选的索引”。key:实际使用的索引。key 为 NULL → 没走索引 → 检查是否索引失效。
rows
优化器估算的扫描行数(基于统计信息,不是精确值)。ANALYZE TABLE 重新抽样更新统计信息。
💭 想一想:为什么
rows只能是”估算”而非精确值?——因为 InnoDB 不维护精确行数(这也是COUNT(*)慢的同一根源),优化器只能靠采样统计信息估算基数;一旦估算失真,选索引就可能选错,于是才有了 FORCE INDEX / ANALYZE TABLE 这套兜底。
Extra —— 附加信息
| 值 | 含义 |
|---|---|
Using index | 覆盖索引,不回表 — 最优 |
Using index condition | ICP 索引下推 |
Using where | 在 Server 层过滤 |
Using filesort | 排序走不了索引,需额外排序 |
Using temporary | 需临时表(GROUP BY、DISTINCT) |
Using join buffer | JOIN 用了 join buffer |
Using filesort + 大数据量表 = 性能杀手。 需要检查 ORDER BY 的列是否在联合索引中。
💭 想一想:为什么
Using filesort是”杀手”而Using where不是?——因为 filesort 意味着排序借不到索引的有序性,必须额外把数据读进内存/磁盘做归并排序,数据一大就产生临时文件 IO;而Using where只是行内过滤,代价小得多。
优化器选错索引怎么办?
FORCE INDEX(idx_name)— 强制使用ANALYZE TABLE— 重新统计OPTIMIZER_TRACE— 看完整代价估算过程
章末提问
追问 1:type=ALL 是不是一定要优化掉?有没有例外?
回答思路:结论先行——不是绝对要优化,要看数据量。因为 ALL 是逐行全表扫描,只有在大表上才是灾难;如果表本身只有几百行,或者该查询是低频后台统计,全表扫描反而是代价最低的选择,强行建索引反而增加写入维护开销。所以判断标准是「扫描行数 × 执行频率」,而不是「是否出现 ALL」。
追问 2:Using filesort 一定慢吗?什么情况下可以接受?
回答思路:结论先行——不一定,取决于排序数据量。因为 filesort 慢在「排序数据放不进 sort_buffer 而落盘产生临时文件 IO」;如果结果集很小、能整体放进内存,filesort 的代价几乎可忽略。真正要警惕的是「大结果集 + filesort」,此时应让 ORDER BY 列命中联合索引的最左有序前缀,借索引有序性免排序。
追问 3:优化器为什么可能选错索引?你如何发现并干预?
回答思路:结论先行——因为优化器靠统计信息做代价估算,估算错了决策就错。因为 InnoDB 的行数和区分度是采样得到的近似值,数据分布不均或长期不更新会让 rows 估算严重失真,优化器可能误判「全表扫描更便宜」。发现靠对比 rows 与真实行数、看 possible_keys 与 key 是否不一致;干预靠 FORCE INDEX 强制、ANALYZE TABLE 重采样、OPTIMIZER_TRACE 看完整代价推导。