MySQL EXPLAIN执行计划到索引优化完全解读
EXPLAIN是MySQL查看执行计划的命令,通过解析查询展示索引使用、联表方式及预估扫描行数。关键字段包括id(执行顺序)、select_type(查询类型,DEPENDENTSUBQUERY为性能杀手,需改JOIN)、type(访问类型,效率从高到低为system、const、eq_ref、ref、range),据此可定位瓶颈并优化索引。
前言
从事数据库调优多年,要说哪款工具最实用,EXPLAIN 绝对位列前茅。任意一条 SELECT 查询,只需在前面加上 EXPLAIN,MySQL 就会将执行计划完整呈现出来——使用哪些索引、如何联表、预估扫描行数、是否存在隐藏瓶颈,一目了然。

本文从实战角度出发,详细拆解 EXPLAIN 输出中的每个字段,同时提供一套可直接应用的优化判断标准。
一、EXPLAIN 是什么?
执行 EXPLAIN SELECT ... 后,MySQL 并不会真正运行该查询,而是返回一个表格形式的执行计划,清晰展示每一步的执行方式。
EXPLAIN SELECT * FROM user WHERE id = 1;
输出结果可能为一行或多行——每一行代表一个步骤,例如单表访问、子查询、UNION 合并等。
二、字段详解(按重要性排序)
1. id —— 步骤编号
数字越大,越先执行
数字相同,从上往下执行
NULL:最后合并步骤(如 UNION)
注意:子查询不一定比主查询后执行,DERIVED 表(派生表)反而会先执行。
2. select_type —— 查询类型
| 值 | 含义 | 性能警示 |
|---|---|---|
| SIMPLE | 简单查询,无子查询、无UNION | 正常 |
| PRIMARY | 外层主查询 | - |
| SUBQUERY | 普通子查询 | 可能慢 |
| DERIVED | FROM 中的子查询(派生表) | 一般慢 |
| UNION | UNION 中第二个及之后的查询 | |
| DEPENDENT SUBQUERY | 相关子查询(依赖外层) | 极慢,必须改 JOIN |
| UNCACHEABLE SUBQUERY | 每次执行都要重算 | 极慢 |
重点盯防:DEPENDENT SUBQUERY 是性能杀手,遇到后基本需要改为 JOIN 才能解决。
3. table —— 访问对象
可能是真实的表名,也可能是类似 (id=2 的派生表)或 这样的临时结构。
4. type —— 访问类型 ★★★★
该字段重要性排前三,直接反映索引的使用效率。性能从好到差排列如下:
| 级别 | 含义 | 说明 |
|---|---|---|
system | 表只有一行 | 系统表,极少见 |
const | 主键/唯一索引等值查询 | 极快,最多匹配一行 |
eq_ref | 联表时被驱动表用主键/唯一索引 | 联表最优 |
ref | 普通索引等值查询 | 好 |
range | 索引范围查询(>, <, IN, BETWEEN) | 尚可 |
index | 扫描整个索引树 | 比全表快一点,但仍需回表 |
ALL | 全表扫描 | 最差,大表必崩 |
优化红线:如果看到 type = ALL 且表数据量超过 10 万行,无需犹豫,立即加索引。
5. possible_keys —— 可能使用的索引
列出候选索引。若该列为 NULL,说明该表没有可用索引,必须创建。
6. key —— 实际使用的索引 ★★★★
为 NULL:未走索引
需与 possible_keys 对比查看:有时存在索引,但 MySQL 未选择(原因隐藏在 Extra 中)
7. key_len —— 使用的索引长度(字节)
该字段有一个妙用:判断联合索引用到了几列。
例如,联合索引 (a, b, c):
key_len = 4:只用了 a(INT)key_len = 8:用了 a、b(均为 INT)key_len = 13:用了 a、b、c(INT + INT + VARCHAR(10))
常见类型的长度参考(utf8mb4):
INT:4 字节
BIGINT:8
VARCHAR(100):
100 * 4 + 2 = 402字节DATETIME:5(5.6+)、8(旧版)
8. ref —— 与索引比较的对象
const:常量,例如WHERE id = 1db.table.column:联表时另一张表的列NULL:非等值查询
9. rows —— 预计扫描行数 ★★★★
注意,这是 MySQL 的估算值,并非精确值。数值越大,查询越慢。优化的最终目标是让 rows 持续减小。
10. filtered —— 过滤后剩余比例(%)
存储引擎返回数据后,经过 WHERE 条件过滤后剩余的比例。越低越好,说明索引筛选能力强。
示例:rows = 1000, filtered = 10 → 预计最终返回 100 行。
11. Extra —— 额外信息 ★★★★★
该字段如同宝藏,直接暴露性能陷阱。
好信号
| 值 | 含义 |
|---|---|
Using index | 覆盖索引,不回表,极快 |
Using where | 有 WHERE 条件过滤 |
警告信号(需优化)
| 值 | 含义 | 处理建议 |
|---|---|---|
Using index condition | 索引条件下推(ICP) | 尚可,但可检查索引顺序 |
Using temporary | 使用了临时表 | GROUP BY 无索引,必须修改 |
Using filesort | 文件排序(内存/磁盘) | ORDER BY 无索引,必须添加 |
Using join buffer | 联表无索引,使用缓存 | 必须为被驱动表加索引 |
危险信号
| 值 | 含义 |
|---|---|
Using temporary; Using filesort | GROUP BY + ORDER BY 完全未走索引 |
Impossible WHERE | WHERE 条件永远为假 |
No tables used | 无 FROM 表 |
三、实战:5 分钟定位慢查询
拿到一条慢 SQL,按照以下顺序逐列审查 EXPLAIN 结果:
第一步:先看type
const/eq_ref/ref→ 安全range→ 尚可index/ALL→ 必须优化
第二步:再看key
为 NULL → 未建索引,立即添加
不为 NULL → 检查是否选对了索引
第三步:看Extra
出现
filesort/temporary/join buffer→ 必须优化出现
Using index→ 优秀
第四步:估算rows
预期只返回几条,但 rows 显示几万 → 索引失效或未走对索引
四、典型案例优化全过程
原始慢查询
EXPLAIN SELECT * FROM order WHERE user_id = 123 ORDER BY create_time DESC;
执行计划:
type: ALL(全表扫描)rows: 500000Extra: Using filesort(文件排序)key: NULL
结论:全表扫描 50 万行,再在内存中排序 → 极慢。
添加索引
ALTER TABLE order ADD INDEX idx_user_time (user_id, create_time);
优化后执行计划
EXPLAIN SELECT * FROM order WHERE user_id = 123 ORDER BY create_time DESC;
type: ref(走索引)key: idx_user_timerows: 15Extra: Using index condition(无 filesort)
效果:从 50 万行扫描降至 15 行,排序也消失了。
五、总结:EXPLAIN 优化口诀
EXPLAIN 看计划,逐行拆开查:
type 是关键,ALL 是红线;
key 不为空,才算走索引;
rows 尽量小,越大越危险;
Extra 藏魔鬼,temporary 必须改,filesort 立即办;
覆盖索引最理想,Using index 放心赞。
写在最后
EXPLAIN 堪称 MySQL 性能优化的入场券。掌握它后,在索引设计、SQL 改写、架构选型方面就能拥有自己的判断力。
但也需明确,EXPLAIN 并非万能:
它给出的是估算,而非精确值
不统计存储引擎之外的消耗(网络、客户端)
不反映锁等待、CPU 开销
因此,EXPLAIN + 慢查询日志 + 性能监控,才是完整的 MySQL 调优体系。
