PostgreSQL EXPLAIN 和 EXPLAIN ANALYZE 怎么看?
简化版
EXPLAIN 用来看 PostgreSQL 计划如何执行 SQL,EXPLAIN ANALYZE 会实际执行 SQL 并输出真实耗时和行数。看执行计划时重点关注扫描方式、JOIN 方式、估算行数和真实行数差距、排序和聚合成本、循环次数,以及是否出现不该有的全表扫描。
详细版
常见执行计划信息包括:
Seq Scan:顺序扫描,可能是全表扫描;Index Scan/Index Only Scan:使用索引扫描;Bitmap Index Scan/Bitmap Heap Scan:先用索引定位,再批量回表;Nested Loop:嵌套循环连接,小结果集常见;Hash Join:哈希连接,大量等值连接常见;Sort:排序节点,可能消耗内存或落盘;cost:优化器估算成本;rows:估算行数;actual time:真实执行耗时;loops:节点执行次数。
优化时不能只看有没有走索引,还要看估算是否准确、返回行数是否合理、索引选择是否真的降低成本。
完整版教学
一、EXPLAIN 和 EXPLAIN ANALYZE 的区别
EXPLAIN 只展示计划,不真正执行语句:
EXPLAIN SELECT * FROM orders WHERE user_id = 100;
EXPLAIN ANALYZE 会实际执行,并输出真实执行信息:
EXPLAIN ANALYZE SELECT * FROM orders WHERE user_id = 100;
这意味着对 INSERT、UPDATE、DELETE 使用 EXPLAIN ANALYZE 时要格外小心,因为语句会真的执行。必要时放在事务中测试并回滚。
二、先看扫描方式
扫描方式通常能快速暴露问题:
Seq Scan:顺序扫描整张表或大范围数据;Index Scan:通过索引定位,再访问表;Index Only Scan:尽量只从索引返回数据;Bitmap Index Scan:用索引找出匹配位置;Bitmap Heap Scan:按位图回表读取数据页。
看到 Seq Scan 不一定就是错。如果表很小,或者条件命中大量数据,顺序扫描可能比索引更快。真正要判断的是:它和业务预期是否一致。
三、重点比较估算行数和真实行数
执行计划里常见:
rows=100
actual rows=50000
如果估算行数和真实行数差距很大,优化器可能会选错 JOIN 顺序、扫描方式或排序策略。
常见原因包括:
- 统计信息过旧;
- 数据分布倾斜;
- 多列条件存在相关性;
- 表刚经历大量写入或删除;
- 表达式、JSONB 字段统计不足。
这时不仅要加索引,也可能需要 ANALYZE、调整统计目标或改写 SQL。
四、看 JOIN 方式是否匹配数据规模
PostgreSQL 常见 JOIN 方式包括:
- Nested Loop:外层结果很小、内层有索引时效果好;
- Hash Join:等值连接、大数据集常见;
- Merge Join:两侧已排序或可高效排序时有优势。
如果外层返回很多行,内层又反复扫描大表,Nested Loop 可能变成性能灾难。此时要检查连接条件、索引、过滤条件下推,以及统计信息是否准确。
五、看排序、聚合和临时开销
ORDER BY、GROUP BY、DISTINCT、窗口函数都可能引入排序或聚合节点。排序数据量大时,可能消耗大量内存,甚至落盘。
优化方向包括:
- 建立匹配排序条件的索引;
- 缩小排序前的数据集;
- 避免无意义的大分页;
- 调整查询结构,让过滤尽早发生;
- 必要时关注
work_mem。
不要只把所有问题归结为“没走索引”,复杂 SQL 的瓶颈经常在排序、聚合和 JOIN。
六、常见误区与追问
| 字段/节点 | 重点看什么 | 典型判断 |
|---|---|---|
actual rows vs rows | 估算是否准确 | 差 100 倍常提示统计信息或数据倾斜问题 |
loops | 节点执行次数 | 内层扫描 loops 很大要警惕 Nested Loop |
Sort Method | 排序是否落盘 | external merge 通常说明内存不足或数据太大 |
易错点:
EXPLAIN ANALYZE会真实执行 SQL。分析写语句时要放在测试环境,或显式开启事务后回滚,别把“看计划”变成“线上改数据”。
比如计划显示 rows=100,但 actual rows=50000,估算差了 500 倍。优化器原本以为外层只有 100 行,选择 Nested Loop;真实执行时外层 50000 行,每行再访问内表一次,就可能把一个毫秒级查询变成秒级查询。这个时候只加索引未必够,还要考虑 ANALYZE、提高统计目标、处理多列相关性或改写 SQL。
- 误区:看到 Seq Scan 就一定要加索引。 小表或命中大比例数据时顺序扫描可能更快,要结合实际行数、成本和耗时判断。
- 误区:EXPLAIN 的 cost 就是真实毫秒数。 cost 是优化器内部估算成本,不等于时间;真实耗时要看
EXPLAIN ANALYZE的actual time。 - 误区:走了索引就一定快。 如果回表很多、过滤很弱、排序和 JOIN 很重,索引扫描也可能慢。
- 追问:估算行数和真实行数差很多怎么办? 先更新统计信息,再看数据倾斜、多列相关性、表达式统计、JSONB 字段统计和 SQL 写法。
- 追问:Nested Loop 什么时候危险? 外层行数大、内层没有高效索引或 loops 很高时危险;小结果集配内层索引时反而可能很好。
- 追问:如何判断排序瓶颈? 看 Sort 节点的输入行数、排序方法、是否落盘,以及是否能用匹配索引或先过滤减少排序数据。
七、加强记忆
看 PostgreSQL 执行计划的顺序是:先区分 EXPLAIN 是否真实执行,再看扫描方式、行数估算、JOIN 策略、排序聚合和循环次数。优化不是看到全表扫描就立刻加索引,而是判断计划和数据规模是否匹配。