SQL 的 EXPLAIN 是诊断查询性能的”X 光片”——它让数据库优化器把”准备怎么执行这条 SQL”摊开给你看,而不真正跑数据。常规用法是在 SELECT/INSERT/UPDATE/DELETE 前加 EXPLAIN 关键字查看执行计划;MySQL 8.0.18+ 与 PostgreSQL 还支持 EXPLAIN ANALYZE 真正执行并回报实际行数与耗时。据 ramidadboub.com 与腾讯云开发者社区的总结,读懂执行计划的关键只有四列:type(怎么查)、key(用了哪个索引)、rows(预估扫多少行)、Extra(额外干了什么)。只要按”type → key → rows → Extra”的顺序读,绝大多数慢查询的根因都能定位。
一、为什么必须先看 EXPLAIN
一条 SQL 从发起到返回,要经历解析、优化、执行三个阶段,其中优化器生成的执行计划直接决定它是秒回还是卡死。据腾讯云开发者社区的文章,性能瓶颈往往藏在”数据访问方式”里——全表扫描、临时表、额外排序、索引没用上,这些都会在执行计划里一目了然。CSDN 的执行计划实战文章把 EXPLAIN 形容为”X 光片”:没它之前优化靠猜,有它之后优化靠证据。
EXPLAIN 不执行 DML,对生产库安全;EXPLAIN ANALYZE 会真实执行(PostgreSQL 建议在事务里回滚以保安全)。
二、MySQL EXPLAIN 的核心字段
EXPLAIN 输出约 12 列,按重要性分三档(综合 CSDN、cnblogs、OneUptime、ramidabdoub.com):
| 重要性 | 字段 | 含义 | 关注点 |
|---|---|---|---|
| 🔴 极高 | type | 访问类型 | ALL 最差,const 最优 |
| 🔴 极高 | key | 实际用的索引 | NULL 表示没用索引 |
| 🔴 极高 | Extra | 额外操作 | 警惕 Using filesort/temporary |
| 🟡 高 | rows | 预估扫描行数 | 越小越好 |
| 🟡 高 | key_len | 索引用了几字节 | 判断联合索引用了几列 |
| 🟡 高 | possible_keys | 候选索引 | 与 key 不一致需警惕 |
| 🟢 中 | id / select_type / table | 执行顺序、查询类型、表名 | 多表/子查询定位用 |
三、type 列:性能风向标
type 是诊断第一眼。它从优到劣排列为:system > const > eq_ref > ref > range > index > ALL(据 techkiz、OneUptime、edsger.dev 一致描述):
- const / system:主键或唯一索引的精确匹配,最多一行,最优。
- eq_ref:JOIN 时对方主键/唯一索引匹配,每行最多一条,很好。
- ref:非唯一索引查找,返回多行,正常。
- range:索引范围扫描(
BETWEEN、IN、>),可接受。 - index:全索引扫描,比全表好但仍遍历整棵树。
- ALL:全表扫描,大表上必须优化。
据 ramidadboub.com 的实战示例,一条 WHERE customer_id=250 ORDER BY order_date 若 type=ALL, key=NULL, rows=980000, Extra=Using filesort,就同时暴露”没用索引 + 全扫 + 额外排序”三重问题;加 (customer_id, order_date) 复合索引后,过滤与排序共用一棵树,性能显著回升。
四、key 与 possible_keys:索引选了没
possible_keys 是优化器”考虑过”的候选索引,key 是它”实际选了”的索引。据 ramidadboub.com 解析,二者差异藏着信号:
key=NULL但possible_keys有值 → 可能是统计信息过期,跑ANALYZE TABLE再试。key=NULL且possible_keys也空 → 要么没建索引,要么函数/类型转换让索引失效(见 13137 篇)。key不在possible_keys中 → 优化器选了更优方案,一般无需干预。
key_len 帮助判断联合索引用了几列:字节数越短,说明只命中了前缀部分列。
五、rows 与 Extra:代价与隐患
rows 是优化器基于统计信息预估的扫描行数,不是实际行数。据 cnblogs 的经验阈值,百万级表期望 rows 控制在千级、千万级表控制在五千级以内;远超则需补索引。filtered(MySQL 5.7+)表示索引查找后 Server 层过滤的剩余百分比,100% 最优。
Extra 是隐患报警器(综合 OneUptime、ramidabdoub.com、腾讯云):
| Extra 值 | 含义 | 处置 |
|---|---|---|
| Using index | 覆盖索引,无需回表 | 最优,保持 |
| Using where | 索引查找后还需过滤 | 关注,看能否并入索引 |
| Using index condition | 索引条件下推(ICP) | 良好,引擎层已过滤 |
| Using filesort | 额外排序,ORDER BY 没走索引 | 加排序列进索引 |
| Using temporary | 建了临时表,常见于 GROUP BY | 优化索引或拆查询 |
六、PostgreSQL 的差异:树状计划与成本
PostgreSQL 的 EXPLAIN 不产表格,而是一棵缩进的计划树,最内层节点缩进最深(据 monpg.app 的对照指南)。每个节点带两个数:启动成本与总成本,单位不是毫秒,而是以 seq_page_cost=1.0 为锚的相对成本。关键阅读习惯:
EXPLAIN (ANALYZE, BUFFERS) SELECT ... ;
- Seq Scan:全表扫描,大表上等于告警,应建索引转 Index Scan。
- actual rows vs estimated rows:估算与实际差距大 → ���计信息过期,跑
ANALYZE表。 - loops=N:嵌套循环内层节点会执行 N 次,实际触碰行数 =
rows × loops,忘乘是 PG 读计划的经典错误。 - Buffers:
shared hit来自缓存、shared read来自磁盘;多 read 说明缓存未命中。
juejin 的 PG 实践强调:永远加 ANALYZE 和 BUFFERS,DML 用 EXPLAIN ANALYZE 时可包在事务里回滚。
七、标准诊断四步法
把下述顺序作为排查固定动作(综合 ramidadboub.com、cnblogs、CSDN、腾讯云):
- 看 type:目标是
ref/range及以上;出现ALL即全表扫描,必须处理。 - 看 key:确认非
NULL;若为NULL,排查缺索引、函数、隐式转换、统计过期。 - 看 rows:过大则说明过滤不足,考虑更精确的索引或拆分查询。
- 看 Extra:消除
Using filesort与Using temporary,优先做成覆盖索引(Using index)。
后续动作:建/改索引后重跑 EXPLAIN 验证,对比 key 是否命中、type 是否提升、rows 是否下降;统计偏差时用 ANALYZE TABLE(MySQL)或 ANALYZE(PG)刷新。
八、EXPLAIN 不是万能
腾讯云开发者社区提醒:执行计划是优化器”此刻基于统计信息”的决策,数据分布、索引状态、系统负载会变,计划也会变。因此分析需结合真实数据量与线上负载动态进行,并在大表结构变更后重新 EXPLAIN。edsger.dev 也给出最佳实践:始终用 EXPLAIN ANALYZE 核对估算与实际的偏差、在类生产数据上测试、统计大变后重新分析。
常见问题(FAQ)
EXPLAIN 会真的执行 SQL 吗?
常规 EXPLAIN 不执行,安全;EXPLAIN ANALYZE 会真实执行,PG 建议包事务回滚。
只看 MySQL 的哪几列就够?
type、key、rows、Extra 四列,按此顺序读即可定位绝大多数问题。
PostgreSQL 的 ANALYZE 和 MySQL 的 ANALYZE TABLE 一样吗?
目的一致(刷新统计信息),但语法不同;都是估算与实际行数偏差大时的首选修复。