目标
查询变慢时,Postgres 能精确打印它做过的一切:每个扫描、连接与排序的真实耗时、实际行数与缓冲命中。认真读这棵树胜过瞎猜索引,因为“实际时间最大的节点 + 估算偏差最宽的节点”同时告诉你该修什么以及优化器为什么选错。一次仔细的阅读通常能替代一下午的索引轮盘赌。
步骤要点
- 用 EXPLAIN (ANALYZE, BUFFERS) 拿到带计时、行数与缓冲的真实计划,而不是纯估算计划。
- 定位时间集中点:把顶层节点实际时间减去子节点时间,找出真正的热点(如 actual time=7800 的嵌套循环)。
- 逐节点对比估算行数与实际行数:估算 12000 而实际 850000 的 Filter 节点暴露统计信息缺口。
- 先 ANALYZE 刷新统计或按计划诉求补 CREATE INDEX CONCURRENTLY,让优化器换更优的连接顺序或扫描策略。
- 重跑同一条 EXPLAIN 记录 Execution Time 从 8000ms 降到两位数,两份计划的 diff 就是提交 PR 的证据。
排错提示
刷过 ANALYZE 估算仍离谱,是列数据倾斜,把统计目标提到 500 再刷;本地计划与生产不同属正常,数据量与设置都不同,应改用 auto_explain 抓生产计划来分析;EXPLAIN ANALYZE 会真执行,写语句务必包在回滚事务里。另注意 actual time 是“每次循环”的耗时,五毫秒×12000 次循环等于一分钟,这是读计划最常见的误读。
适用边界
EXPLAIN ANALYZE 会实际执行查询,破坏性语句绝不能在回滚事务之外跑;计划依赖数据分布,数据增长或大版本升级后今天验证过的计划可能失效。
代码示例
EXPLAIN (ANALYZE, BUFFERS)
SELECT o.id, c.name
FROM orders o
JOIN customers c ON c.id = o.customer_id
WHERE o.created_at > now() - interval '30 days';
-- Key lines to compare:
-- rows=12000 estimated ... rows=850000 actual (statistics gap)
-- actual time=7800.12..7800.13 loops=1 (hotspot node)
-- Execution Time: 8003.5 ms