EXPLAIN 怎么读
EXPLAIN 把优化器的执行计划摊开来看:用哪张表、用什么访问方式、命中哪个索引、预计扫描多少行、还有哪些额外动作。读计划要回答三个问题——是否走了索引、扫描行数是否可接受、有没有额外的排序或临时表。它给出的是基于统计信息的估算,要拿到真实行数与耗时,需要用能实际执行语句的分析写法。
基本信息
| 用途 | 查看语句的执行计划 |
|---|---|
| 语法 | EXPLAIN 语句 / EXPLAIN FORMAT=JSON 语句 |
| 关键列 | type / key / key_len / rows / filtered / Extra |
| 访问类型顺序 | const → eq_ref → ref → range → index → ALL |
| 行数口径 | rows 为估算值,来自统计信息 |
| 实测手段 | 8.0.18 起可实际执行语句获取真实行数 |
典型使用场景
- 慢查询优化的第一步:先看计划,再决定加索引还是改写法
- 上线前评审:新语句是否存在全表扫描
- 索引效果验收:加索引前后对比访问类型、使用索引与扫描行数
- 分页与排序排查:确认排序是否借用了索引顺序
- 连接查询排查:确认被驱动表是否走了索引
- 核心语句的定期巡检:发现执行计划悄悄发生变化
使用与解读
使用与解读
EXPLAIN SELECT id, amount FROM orders # 只取需要的列,计划更贴近真实
WHERE user_id = 42 AND status = 1 # 等值条件,便于观察索引命中
ORDER BY created_at DESC LIMIT 10;
EXPLAIN FORMAT=JSON SELECT ... ; # 结构化输出,附带成本估算
EXPLAIN ANALYZE SELECT ... ; # 8.0.18 起实际执行,返回真实行数与耗时常见计划改进
常见计划改进
EXPLAIN SELECT id FROM orders WHERE status = 1; # 条件列无索引,计划里通常是全表扫描
ALTER TABLE orders ADD INDEX idx_status_created (status, created_at); # 补上复合索引
EXPLAIN SELECT id FROM orders WHERE status = 1 ORDER BY created_at DESC LIMIT 10; # 再看访问类型与附加信息逐列读法
| 列 | 含义 | 异常信号 |
|---|---|---|
| id | 查询块序号,同号按从上到下执行 | 子查询会出现多个编号 |
| select_type | 查询类型,如简单、子查询、联合 | 出现子查询要评估能否改写 |
| table | 当前访问的对象 | 排在前面的通常是驱动表 |
| type | 访问类型,性能第一指标 | ALL 是全表扫描,优先优化 |
| possible_keys | 候选索引 | 为空说明没有可用索引 |
| key | 实际使用的索引 | 为 NULL 说明索引没用上 |
| key_len | 索引使用长度 | 可判断复合索引用到几列 |
| rows | 估算扫描行数 | 数值巨大是成本主要来源 |
EXPLAIN 只做估算不执行语句,行数来自统计信息,可能与真实值差一个数量级;需要实测时用 8.0.18 起的分析写法,它会真正执行语句并给出实际行数。
不能只看是否用上了索引:复合索引被命中的前缀不对时,虽然显示了索引名,过滤效果依然很差,要结合索引使用长度与估算行数一起判断。
对生产语句直接做实测分析会真正执行这条语句并产生负载,写语句尤其危险;慢查询排查应安排在只读从库或业务低峰期进行。
计划检查清单
- 访问类型是否落在等值或范围级别,有无全表扫描
- 实际使用的索引是否与预期一致,复合索引用到几列
- 估算扫描行数与最终返回行数的比例是否合理
- 是否出现额外排序或临时表
- 连接查询中被驱动表是否走了索引
- 统计信息是否过期,是否需要重新收集后再看计划
同一语句在不同数据量下会得到不同计划:统计信息更新、数据分布变化都会让优化器改选索引,因此核心语句的执行计划要定期取样比对,而不是上线时看一次就存档。