Skip to content
Go back

MySQL EXPLAIN——执行计划每个字段的完整解读

MySQL EXPLAIN:读懂执行计划

一句话结论(30s)

EXPLAIN 读执行计划的本质是看优化器如何用索引和估算代价,因为 type 从 const 到 ALL 逐级变差、直接反映扫描方式的优劣。关键设计是三个信号:key 为空说明没走索引、rows 是估算扫描行数、ExtraUsing 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 是估算扫描行数(基于统计信息,非精确值)。ExtraUsing 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       → 全表扫描 ← 需要优化

constALL 逐级更差。出现 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 conditionICP 索引下推
Using where在 Server 层过滤
Using filesort排序走不了索引,需额外排序
Using temporary需临时表(GROUP BY、DISTINCT)
Using join bufferJOIN 用了 join buffer

Using filesort + 大数据量表 = 性能杀手。 需要检查 ORDER BY 的列是否在联合索引中。

💭 想一想:为什么 Using filesort 是”杀手”而 Using where 不是?——因为 filesort 意味着排序借不到索引的有序性,必须额外把数据读进内存/磁盘做归并排序,数据一大就产生临时文件 IO;而 Using where 只是行内过滤,代价小得多。

优化器选错索引怎么办?

  1. FORCE INDEX(idx_name) — 强制使用
  2. ANALYZE TABLE — 重新统计
  3. OPTIMIZER_TRACE — 看完整代价估算过程

章末提问

追问 1:type=ALL 是不是一定要优化掉?有没有例外?

回答思路:结论先行——不是绝对要优化,要看数据量。因为 ALL 是逐行全表扫描,只有在大表上才是灾难;如果表本身只有几百行,或者该查询是低频后台统计,全表扫描反而是代价最低的选择,强行建索引反而增加写入维护开销。所以判断标准是「扫描行数 × 执行频率」,而不是「是否出现 ALL」。

追问 2:Using filesort 一定慢吗?什么情况下可以接受?

回答思路:结论先行——不一定,取决于排序数据量。因为 filesort 慢在「排序数据放不进 sort_buffer 而落盘产生临时文件 IO」;如果结果集很小、能整体放进内存,filesort 的代价几乎可忽略。真正要警惕的是「大结果集 + filesort」,此时应让 ORDER BY 列命中联合索引的最左有序前缀,借索引有序性免排序。

追问 3:优化器为什么可能选错索引?你如何发现并干预?

回答思路:结论先行——因为优化器靠统计信息做代价估算,估算错了决策就错。因为 InnoDB 的行数和区分度是采样得到的近似值,数据分布不均或长期不更新会让 rows 估算严重失真,优化器可能误判「全表扫描更便宜」。发现靠对比 rows 与真实行数、看 possible_keyskey 是否不一致;干预靠 FORCE INDEX 强制、ANALYZE TABLE 重采样、OPTIMIZER_TRACE 看完整代价推导。


Share this post on:

Previous Post
MySQL GTID——为什么比binlog position更可靠
Next Post
MySQL InnoDB的BufferPool——分区LRU与Double Write