MySQL EXPLAIN 查询计划详解
EXPLAIN 用于查看 MySQL 为一条 SQL 语句生成的执行计划。通过执行计划可以分析访问顺序、索引选择、预估扫描行数以及排序、临时表等信息。
DESC 与 EXPLAIN 的区别
查看表结构
DESC 是 DESCRIBE 的缩写,用于查看表或视图的字段信息。
DESC employee;
DESCRIBE employee;
这两条语句通常会显示字段名、数据类型、是否允许为 NULL、索引类型、默认值和附加信息。
查看 SQL 执行计划
在查询语句前添加 EXPLAIN,可以查看优化器选择的执行计划。
EXPLAIN
SELECT *
FROM employee
WHERE first_name = 'tom';

MySQL 还支持其他输出格式。例如,树形格式更便于观察执行顺序:
EXPLAIN FORMAT=TREE
SELECT *
FROM employee
WHERE first_name = 'tom';
EXPLAIN ANALYZE 会实际执行 SQL,并返回估算值与实际执行数据。对于更新或删除语句应谨慎使用,避免意外修改数据。
EXPLAIN ANALYZE
SELECT *
FROM employee
WHERE first_name = 'tom';
EXPLAIN 常见列

传统表格格式通常包含以下列:
| 列名 | 作用 |
|---|---|
id | 查询块编号,可辅助判断查询块之间的执行关系 |
select_type | 查询块的类型,例如简单查询、主查询、子查询或派生表 |
table | 当前访问的表、别名或内部临时结果 |
partitions | 预计访问的分区;未使用分区表时通常为 NULL |
type | 表访问方式,是判断索引使用情况的重要指标 |
possible_keys | 优化器认为可能使用的索引 |
key | 实际选择的索引 |
key_len | 预计使用的索引键长度,单位为字节 |
ref | 与索引列比较的常量或列 |
rows | 优化器估算需要检查的行数 |
filtered | 经过当前表条件过滤后预计保留的百分比 |
Extra | 排序、临时表、覆盖索引等补充信息 |
id:查询块编号
id 用于标识不同的查询块,但不能简单地把它当作绝对执行顺序。
id相同的多行通常属于同一个查询块,表的显示顺序反映该连接计划中的访问顺序。id不同通常表示存在子查询、派生表或UNION等多个查询块。- 某些结果行的
id可能为NULL,例如UNION RESULT。
复杂 SQL 的真实执行过程还应结合 FORMAT=TREE 或 EXPLAIN ANALYZE 判断。
select_type:查询类型
常见取值如下:
| 取值 | 含义 |
|---|---|
SIMPLE | 不包含 UNION 或子查询的简单查询 |
PRIMARY | 最外层查询 |
UNION | UNION 中第二个及后续查询块 |
DEPENDENT UNION | 依赖外层查询结果的 UNION 查询块 |
UNION RESULT | UNION 结果集 |
SUBQUERY | 不依赖外层查询的子查询 |
DEPENDENT SUBQUERY | 依赖外层查询结果的相关子查询 |
DERIVED | FROM 子句中的派生表 |
MATERIALIZED | 被物化后重复使用的子查询结果 |
table:访问对象
table 一般显示表名或表别名。对于优化器生成的内部结果,可能出现以下形式:
<derivedN>:来自编号为N的派生表查询块。<unionM,N>:来自编号为M、N等查询块的UNION结果。<subqueryN>:被物化的子查询结果。
type:访问方式
type 描述 MySQL 如何从表中读取数据。下面按通常情况下由精确到宽泛的顺序列出常见类型,但实际性能还受返回行数、数据分布和缓存等因素影响。
| 类型 | 说明 |
|---|---|
system | 表被视为只有一行,是 const 的特例 |
const | 通过主键或唯一索引等值匹配,最多返回一行 |
eq_ref | 连接时通过主键或非空唯一索引匹配,每个前表组合最多命中一行 |
ref | 通过普通索引或唯一索引的非完整前缀进行等值查找,可能返回多行 |
fulltext | 使用全文索引 |
ref_or_null | 类似 ref,同时额外查找 NULL |
index_merge | 合并多个索引的扫描结果 |
unique_subquery | 某些 IN 子查询通过唯一索引查找 |
index_subquery | 某些 IN 子查询通过非唯一索引查找 |
range | 对索引执行范围扫描 |
index | 扫描整个索引 |
ALL | 扫描整张表 |
分析时通常需要重点关注 ALL 和 index。不过小表全表扫描不一定是问题,不能只根据 type 单独判断 SQL 是否需要优化。
索引相关列
possible_keys
显示优化器认为可能使用的索引。值为 NULL 表示当前查询没有明显可用索引,但这并不等同于 SQL 一定存在性能问题。
key
显示优化器最终选择的索引。即使 possible_keys 列出了多个索引,key 通常也只显示实际采用的索引;使用 index_merge 时可能显示多个索引。
key_len
显示执行计划预计使用的索引键长度。对于联合索引,可以根据该值辅助判断使用到了哪些索引列。
key_len 是估算的最大长度,不代表实际读取的数据长度。字符集、可空列、变长字段和索引列类型都会影响该值。
ref
显示索引查找时用于比较的值。例如:
const:与常量比较。库名.表名.列名:与另一张表的列比较。func:与表达式、函数结果或发生类型转换的值比较。
rows 与 filtered
rows
rows 是优化器估算需要检查的行数,并非实际扫描行数。统计信息不准确时,估算结果也可能出现偏差。
filtered
filtered 是预计通过当前表条件过滤后保留的数据比例,单位为百分比。
可以用下面的方式粗略估算传递给下一步的行数:
rows × filtered ÷ 100
若需要观察实际执行行数和耗时,应使用 EXPLAIN ANALYZE。
Extra:补充信息
Extra 中常见信息如下:
| 信息 | 说明 |
|---|---|
Using index | 使用覆盖索引即可获得所需列,通常不需要回表 |
Using where | 读取数据后还需要根据条件进行过滤 |
Using index condition | 使用索引条件下推,在存储引擎层过滤部分记录 |
Using filesort | 排序无法直接利用合适的索引,需要额外排序;不一定真的写磁盘文件 |
Using temporary | 使用内部临时表保存中间结果,常见于部分分组、去重或排序操作 |
Using join buffer | 连接过程使用连接缓冲区,通常需要检查连接条件和索引 |
FirstMatch | 半连接优化策略之一,找到首个匹配后停止继续查找 |
LooseScan | 半连接优化策略之一,通过索引跳跃扫描减少重复匹配 |
Impossible WHERE | 优化器判断查询条件不可能成立 |
Extra 可以同时出现多项信息。例如:
Using index condition; Using where
基本分析思路
分析执行计划时,可以按以下顺序检查:
-
确认表的访问顺序是否合理。
-
检查
type是否出现大表的ALL或不必要的index扫描。 -
对比
possible_keys与key,确认索引是否被选中。 -
结合
key_len判断联合索引的使用范围。 -
关注
rows、filtered和Extra中的排序、临时表及连接缓冲信息。 -
对关键 SQL 使用
EXPLAIN ANALYZE,以实际行数和耗时验证估算结果。
喜欢的话,留下你的评论吧~