MySQL EXPLAIN
官方文档:
- https://dev.mysql.com/doc/refman/8.0/en/explain.html
- https://dev.mysql.com/doc/refman/8.4/en/explain.html
- https://www.mysql.net.cn/doc/refman/8.0/en/explain.html
其它文档:
MySQL EXPLAIN 是一个用于分析 SQL 查询执行计划的工具,它模拟优化器执行查询,不会实际运行,而是返回关于表访问顺序、索引使用、扫描行数等性能相关的信息,帮助识别和优化性能瓶颈。
- 显示 MySQL 如何处理 SQL 语句。
- 分析查询语句或表结构的性能瓶颈。
- 查看优化器选择的执行路径是否符合预期。
- id: 查询的序列号。
- select_type: 查询类型(SIMPLE, PRIMARY, SUBQUERY, UNION 等)。
- table: 表名或别名。
- type: 访问类型(从好到坏:system, const, eq_ref, ref, range, index, ALL)。
- possible_keys: 可能用到的索引。
- key: 实际使用的索引。
- key_len: 使用的索引长度。
- ref: 与索引比较的列或常量。
- rows: 估算的扫描行数。
- Extra: 额外信息(如 Using index, Using filesort, Using temporary 等)。
EXPLAIN SELECT * FROM users WHERE status = 'active' AND created_at > '2025-01-01';
EXPLAIN 是 MySQL 执行计划分析工具,用来判断 SQL 有没有走索引、怎么走、代价多大、慢在哪里。
EXPLAIN 用于回答 6 个核心问题:
- 用了哪些表?
- 表的访问顺序?
- 用没用索引?用的是哪个?
- 是全表扫描还是索引扫描?
- 预计扫描多少行?
- 有没有额外开销(排序、临时表、回表等)?
EXPLAIN SELECT * FROM user WHERE id = 1;
MySQL 8 推荐:
EXPLAIN ANALYZE SELECT * FROM user WHERE id = 1;
EXPLAIN ANALYZE 会 真正执行 SQL,并给出真实耗时(非常重要)
常见字段(按重要程度):
| 字段 | 重要性 | 含义 |
|---|---|---|
| id | ⭐⭐⭐ | 查询层级 / 执行顺序 |
| select_type | ⭐⭐⭐ | 查询类型 |
| table | ⭐⭐ | 访问的表 |
| type | ⭐⭐⭐⭐ | 访问方式(最重要) |
| possible_keys | ⭐⭐ | 可能使用的索引 |
| key | ⭐⭐⭐⭐ | 实际使用的索引 |
| key_len | ⭐⭐ | 索引使用长度 |
| ref | ⭐⭐ | 索引比较方式 |
| rows | ⭐⭐⭐ | 预计扫描行数 |
| filtered | ⭐⭐ | 条件过滤比例 |
| Extra | ⭐⭐⭐⭐ | 额外信息(非常关键) |
规则总结
- id 越大,越先执行
- id 相同 -> 从上往下执行
- 子查询 id > 外层查询
示例:
SELECT * FROM A
WHERE id IN (
SELECT id FROM B
);
执行顺序:
- 子查询 B(id=2)
- 外层查询 A(id=1)
| 类型 | 说明 |
|---|---|
| SIMPLE | 简单查询(无子查询) |
| PRIMARY | 主查询 |
| SUBQUERY | 子查询 |
| DERIVED | 派生表(FROM 子查询) |
| UNION | UNION 中的第二个查询 |
| UNION RESULT | UNION 合并结果 |
⚠️ DERIVED 常是性能风险点(会生成临时表)
从好到坏排序:
| type | 说明 |
|---|---|
| system | 系统表 |
| const | 主键 / 唯一索引等值 |
| eq_ref | 唯一索引 JOIN |
| ref | 非唯一索引 |
| range | 范围扫描 |
| index | 全索引扫描 |
| ALL | 全表扫描 ❌ |
记忆口诀:
const > eq_ref > ref > range > index > ALL
示例对比
✅ 好 SQL
SELECT * FROM user WHERE id = 1;
type = const
⚠️ 可接受
SELECT * FROM user WHERE age BETWEEN 20 AND 30;
type = range
❌ 危险
SELECT * FROM user WHERE name LIKE '%abc';
type = ALL
- possible_keys:理论上能用的索引
- key:实际用到的索引
⚠️ 如果:
possible_keys = idx_a, idx_b
key = NULL
说明:优化器判断全表扫描更快(很危险)
- 反映 联合索引命中到哪一列
- 单位是 字节
例子:
INDEX idx(a, b, c)
WHERE a=1 AND b=2
-> key_len = a + b
⚠️ 用于判断 最左前缀是否完整使用
常见值:
| ref 值 | 含义 |
|---|---|
| const | 常量匹配 |
| table.col | 表字段比较 |
| func | 函数计算(通常不好) |
- 这是优化最直观指标
- SQL 优化的核心目标:减少 rows
⚠️ rows 很小但 SQL 慢:
- 回表多
- filesort
- 临时表
- 网络 IO
filtered = 10
表示:
扫描的 rows 中,只有 10% 能命中 WHERE 条件
rows × filtered ≈ 最终结果行数
| Extra | 含义 |
|---|---|
| Using index | 覆盖索引(非常好) |
| Using where | 使用了条件过滤 |
| Using index condition | ICP 下推 |
| Extra | 含义 |
|---|---|
| Using filesort | 额外排序(慢) |
| Using temporary | 使用临时表 |
| Range checked for each record | 每行重新计算索引 |
| Using join buffer | Join 未走索引 |
EXPLAIN ANALYZE SELECT * FROM orders WHERE user_id = 100;
输出:
- 实际执行时间
- 实际扫描行数
- 真实成本
📌 对比 EXPLAIN 和 ANALYZE:
- EXPLAIN -> 预估
- ANALYZE -> 实际
原则:
- 小表驱动大表
- JOIN 字段必须有索引
- 被驱动表 type 至少 ref
SELECT *
FROM order o
JOIN user u ON o.user_id = u.id;
EXPLAIN 应该看到:
- u.id 使用 PRIMARY
- o.user_id 有索引
type=ALL
解决:
- 改成 xxx%
- 使用全文索引
- ES / 搜索引擎
WHERE DATE(create_time)='2024-01-01'
解决:
WHERE create_time >= '2024-01-01'
AND create_time < '2024-01-02'
解决:
- where + order by 用同一个索引
- 避免跨索引排序
解决:
- 拆成 UNION
- 使用同一索引字段
一看 type,二看 key,三看 rows,四看 Extra
目标:
- type ≥ range
- rows 越小越好
- 没有 Using filesort / temporary
- 能 Using index 就最好
Q:EXPLAIN 看哪些字段最重要?
A:type、key、rows、Extra
Q:type=ALL 一定慢吗?
A:大表一定慢,小表可接受
Q:Using index 一定最好吗?
A:是(覆盖索引,避免回表)
Q:EXPLAIN 能看到锁吗?
A:不能(需 performance_schema)
EXPLAIN 是 SQL 性能的“X 光片”,真正的高手不是会写 SQL,而是会读 EXPLAIN。