前言
在 MySQL 数据库调优中,EXPLAIN 是开发者最强大的"照妖镜"。任何 SELECT 查询,只要在前面加上 EXPLAIN,MySQL 就会告诉你它打算怎么执行这条 SQL------用哪些索引、联表顺序、扫描多少行、有没有隐性问题。
本文将从实际开发视角,逐一拆解 EXPLAIN 输出中的每个字段,并给出可直接落地的优化判断标准。
一、EXPLAIN 是什么?
执行 EXPLAIN SELECT ... 后,MySQL 不会真实执行查询,而是返回执行计划,以表格形式展示每一步如何操作。
sql
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 ------ 访问对象
可以是真实表名,也可能是 <derived2>(id=2 的派生表)或 <union1,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 = 1) -
db.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 显示几万 → 索引失效或没走对索引
四、典型案例优化全过程
🚨 原始慢查询
sql
EXPLAIN
SELECT * FROM order
WHERE user_id = 123
ORDER BY create_time DESC;
执行计划:
-
type: ALL(全表扫描) -
rows: 500000 -
Extra: Using filesort(文件排序) -
key: NULL
结论 :全表扫 50 万行,再在内存排序 → 极慢。
✅ 添加索引
sql
ALTER TABLE order ADD INDEX idx_user_time (user_id, create_time);
🚀 优化后执行计划
sql
EXPLAIN
SELECT * FROM order
WHERE user_id = 123
ORDER BY create_time DESC;
-
type: ref(走索引) -
key: idx_user_time -
rows: 15 -
Extra: Using index condition(无 filesort)
效果 :50万行扫描 → 15行,排序消失。
五、总结:EXPLAIN 优化口诀
text
EXPLAIN 看计划,逐行拆开查:
type 是关键,ALL 是红线;
key 不为空,才算走索引;
rows 尽量小,越大越危险;
Extra 藏魔鬼,temporary 必须改,filesort 立即办;
覆盖索引最理想,Using index 放心赞。
写在最后
EXPLAIN 是 MySQL 性能优化的入场券。掌握它,你就能在索引设计、SQL 改写、架构选型上拥有判断力。
但 EXPLAIN 不是万能的:
-
它是估算,不是精确值
-
不统计存储引擎外的消耗(网络、客户端)
-
不反映锁等待、CPU 开销
因此,EXPLAIN + 慢查询日志 + 性能监控,才是完整的 MySQL 调优体系。