← 中文资源

简体中文版本

读懂 Postgres 执行计划并修复慢节点

运行 EXPLAIN ANALYZE 逐节点走读执行计划,对比估算与实际行数,对成本集中的节点对症下药。

阅读英文原文

目标

查询变慢时,Postgres 能精确打印它做过的一切:每个扫描、连接与排序的真实耗时、实际行数与缓冲命中。认真读这棵树胜过瞎猜索引,因为“实际时间最大的节点 + 估算偏差最宽的节点”同时告诉你该修什么以及优化器为什么选错。一次仔细的阅读通常能替代一下午的索引轮盘赌。

步骤要点

  1. 用 EXPLAIN (ANALYZE, BUFFERS) 拿到带计时、行数与缓冲的真实计划,而不是纯估算计划。
  2. 定位时间集中点:把顶层节点实际时间减去子节点时间,找出真正的热点(如 actual time=7800 的嵌套循环)。
  3. 逐节点对比估算行数与实际行数:估算 12000 而实际 850000 的 Filter 节点暴露统计信息缺口。
  4. 先 ANALYZE 刷新统计或按计划诉求补 CREATE INDEX CONCURRENTLY,让优化器换更优的连接顺序或扫描策略。
  5. 重跑同一条 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