EXPLAIN 是什么
EXPLAIN 在不真正执行语句的前提下,返回优化器选择的访问路径:每张表怎么访问、用哪个索引、预估扫描多少行。它是排查慢查询的起点,多数慢 SQL 的问题能直接从输出里看出来。普通 EXPLAIN 只做估算,EXPLAIN ANALYZE 会真正执行语句并给出真实行数与耗时,MySQL 8.0.18 起与 PostgreSQL 都支持。EXPLAIN 也可用于 UPDATE 与 DELETE:只做分析,不修改数据。
基本信息
| 作用 | 查看优化器为语句选择的执行计划,不执行语句 |
|---|---|
| 适用语句 | SELECT、UPDATE、DELETE,视数据库而定 |
| MySQL 格式 | EXPLAIN 或 EXPLAIN FORMAT=TREE / JSON |
| PostgreSQL 格式 | EXPLAIN 或 EXPLAIN (ANALYZE, BUFFERS, VERBOSE) |
| 关键列 | type、key、key_len、rows、filtered、Extra |
| 前提 | 统计信息准确,否则估算行数与实际偏差很大 |
典型使用场景
- 慢查询定位:先看访问方式,再决定加索引还是改 SQL
- 上线前评估:变更查询前确认不会出现全表扫描
- 索引验证:新建索引后确认 key 列真的被选中
- 连接顺序检查:多表连接时看驱动表是不是小结果集
- 统计信息核对:rows 与实际差距大时考虑更新统计信息
- 写语句预检:用 EXPLAIN 看 UPDATE 或 DELETE 会扫多少行
示例
示例
EXPLAIN SELECT o.id, o.total
FROM orders AS o
WHERE o.user_id = 100 # 期望走 user_id 上的索引
ORDER BY o.created_at DESC
LIMIT 10;
-- 也可用于写语句,只分析不执行
EXPLAIN UPDATE orders SET status = 'done' WHERE id = 1;
EXPLAIN DELETE FROM orders WHERE created_at < '2023-01-01';
-- MySQL 8.0:树形与 JSON 格式更易读
EXPLAIN FORMAT=TREE SELECT * FROM orders WHERE user_id = 100;
-- PostgreSQL:附带真实执行数据
EXPLAIN (ANALYZE, BUFFERS) SELECT * FROM orders WHERE user_id = 100;读计划先看什么
| 看哪一列 | 期望值 | 异常信号 |
|---|---|---|
| type | const / eq_ref / ref / range | ALL 或 index 说明扫描范围大 |
| key | 命中了预期索引 | NULL 表示没走索引 |
| key_len | 与联合索引前缀长度相符 | 偏短说明只用到前几列 |
| rows | 估算扫描行数较小 | 数量级明显偏大说明过滤不足 |
| filtered(MySQL) | 越接近 100 越好 | 很低说明大量行被丢弃 |
| Extra | 无告警项 | Using filesort、Using temporary 值得关注 |
排查顺序:先 EXPLAIN 看访问方式,再判断该加索引、改 SQL 还是更新统计信息,不要凭感觉加索引。
EXPLAIN ANALYZE 会真正执行语句,对更新与删除类语句要特别小心;分析写语句时优先用普通 EXPLAIN。
在数据量很小的开发库上看计划没有参考价值:优化器在不同数据量级下会给出完全不同的选择,评估要贴近生产数据量。
统计信息不准时计划会跑偏,常见诱因是大量写入后未更新统计信息;MySQL 可执行 ANALYZE TABLE,PostgreSQL 可执行 ANALYZE。
排查路径
- 先看有没有全表扫描:type 为 ALL 或 index 时,确认是过滤条件缺失还是索引没建
- 再看索引是否被选中:key 为空要检查条件的写法、类型匹配与是否被函数包裹
- 看扫描行数与返回行数的比例:比例过大说明过滤发生在读取之后
- 看排序与临时表:Extra 里的 Using filesort 与 Using temporary 是典型额外开销
- 改前改后各跑一次计划:加索引或改写法后确认变化符合预期
- 结合慢日志定位重点:先按耗时与执行次数排序,再逐个看计划
- 统计信息异常时先更新:MySQL 用 ANALYZE TABLE,PostgreSQL 用 ANALYZE
计划解读误区
- 估算行数不等于实际行数:rows 是统计模型给的估值,偏差明显时要用 EXPLAIN ANALYZE 实测
- possible_keys 有值不代表用了索引:只有 key 列才是真正被选中的索引
- type 为 index 不等于优化到位:它扫描整棵索引树,代价接近全表扫描
- Extra 出现 Using where 未必是问题,要看它发生在索引层还是回表之后
- 计划会随数据量变化:上线后数据增长,原本正常的计划可能突然变成全表扫描
- 同一条 SQL 用不同参数会得到不同计划,评估时要覆盖典型参数而不是只试一个值