03 - 用 EXPLAIN 读懂执行计划
优化 SQL 不能靠猜。EXPLAIN 是数据库给你的「透视镜」——它告诉你数据库打算怎么执行这条 SQL:走不走索引、要扫描多少行、慢在哪。学会看它,你就有了优化的「眼睛」。
一句话先记住:
EXPLAIN让你看到数据库的「执行计划」,从此优化 SQL 从「凭感觉」变成「看数据」。
3.1 EXPLAIN 和 EXPLAIN ANALYZE
-- EXPLAIN:只「预估」执行计划,不真正执行(快,安全)
EXPLAIN SELECT * FROM users WHERE email = 'a@x.com';
-- EXPLAIN ANALYZE:真正执行一遍,给出「实际」耗时和行数(更准,但会真的跑)
EXPLAIN ANALYZE SELECT * FROM users WHERE email = 'a@x.com';
| 命令 | 是否真执行 | 用途 |
|---|---|---|
EXPLAIN | 否,只估算 | 快速看计划、看走不走索引 |
EXPLAIN ANALYZE | 是,真跑 | 看真实耗时、估算准不准 |
⚠️
EXPLAIN ANALYZE对UPDATE/DELETE会真的执行!要在事务里跑并回滚,或只在测试库用。
3.2 最该看的:扫描方式
执行计划里最关键的信息,是数据库用什么方式「找数据」。PostgreSQL 常见几种:
Seq Scan(顺序扫描 / 全表扫描)
→ 一行行看整张表。大表上出现它,通常是「没走索引」的信号 ⚠️
Index Scan(索引扫描)
→ 走了索引定位,再回表取数据。通常是好事 ✅
Index Only Scan(仅索引扫描)
→ 走了索引,且数据全在索引里,不用回表。最理想 ✅✅(覆盖索引生效)
Bitmap Index Scan / Bitmap Heap Scan
→ 介于两者之间,匹配行较多时用,先收集位置再批量取
看执行计划第一眼:大表上是不是出现了
Seq Scan?如果是,且这条查询应该走索引,那就是优化点。
3.3 看懂关键数字
以一段(简化的)EXPLAIN 输出为例:
Index Scan using idx_email on users
(cost=0.42..8.44 rows=1 width=64) (actual time=0.03..0.04 rows=1 loops=1)
拆解几个关键字段:
| 字段 | 含义 | 怎么看 |
|---|---|---|
Index Scan using idx_email | 用了 idx_email 这个索引 | 确认走了哪个索引 ✅ |
cost=0.42..8.44 | 估算的启动成本..总成本 | 数值越大越慢(相对值,用于比较) |
rows=1 | 估算要处理的行数 | 越少越好 |
actual time=0.03..0.04 | 实际耗时(毫秒) | 只有 ANALYZE 才有 |
actual ... rows=1 | 实际处理的行数 | 和估算 rows 对比 |
一个重要技巧:对比「估算行数」和「实际行数」
如果 估算 rows=1,但 实际 rows=100000
→ 说明数据库的「统计信息过时了」,估错了,可能选了错的计划
→ 解决:ANALYZE users; (更新统计信息,让优化器估得准)
3.4 一个优化前后的对比示例
-- 优化前:email 没索引
EXPLAIN ANALYZE SELECT * FROM users WHERE email = 'a@x.com';
Seq Scan on users (cost=0.00..18334 rows=1)
(actual time=120.5..120.5 rows=1) ← 全表扫描,扫了整张表,120ms
-- 建索引
CREATE INDEX idx_email ON users (email);
-- 优化后
EXPLAIN ANALYZE SELECT * FROM users WHERE email = 'a@x.com';
Index Scan using idx_email on users (cost=0.42..8.44 rows=1)
(actual time=0.03..0.04 rows=1) ← 走索引,0.04ms,快了几千倍
对比一目了然:Seq Scan → Index Scan,耗时从 120ms 到 0.04ms。这就是用 EXPLAIN 验证优化效果的标准姿势。
3.5 还要留意的几个「慢信号」
· Seq Scan on 大表 → 该查询可能缺索引
· Nested Loop + 大 rows → JOIN 可能没用好索引,数据量大时很慢
· Sort(排序)耗时高 → ORDER BY 的列可以考虑建索引来免排序
· 估算 rows 和实际差很大 → 统计信息过时,ANALYZE 一下
· Rows Removed by Filter 很大 → 扫了很多行但大部分被过滤掉,索引不够精准
3.6 使用建议
优化任何一条慢 SQL 的标准动作:
1. EXPLAIN ANALYZE 跑一遍,看扫描方式和耗时
2. 是 Seq Scan 且不该是 → 建索引 / 改写 SQL
3. 改完再 EXPLAIN ANALYZE,对比前后
4. 关注实际耗时 actual time 有没有真的降下来
心态:不要凭感觉说「加个索引应该快了」。用 EXPLAIN ANALYZE 拿数据说话,优化才靠谱。
本章小结
| 你要记住 | 要点 |
|---|---|
| 两个命令 | EXPLAIN(估算)、EXPLAIN ANALYZE(真跑,有实际耗时) |
| 看扫描方式 | Seq Scan(全表,警惕)、Index Scan(走索引,好)、Index Only Scan(免回表,最好) |
| 看关键数字 | cost、估算 rows vs 实际 rows、actual time |
| 统计信息 | 估算和实际差很大就 ANALYZE 更新统计 |
| 核心心态 | 优化用数据验证,别凭感觉 |
下一章预告:索引三章讲完,能解决大部分问题。但优化不只有索引——SQL 写法、表结构设计本身也大有讲究。
上一章 ← 02 - 索引优化实战 | 下一章 → 04 - SQL 与表结构优化