阅读 PostgreSQL 中的 EXPLAIN ANALYZE 输出而不迷失
PostgreSQL EXPLAIN ANALYZE tutorial. Learn query plan interpretation, bottleneck detection, and database performance optimization.
- AI
- MIT
- 更新于 2026-05-15
{</* 资源信息 */>} 第一次看到 Postgres EXPLAIN ANALYZE 输出时,它看起来像
一棵数字圣诞树。 对于你提出的问题来说,大多数都是噪音
实际上有:为什么这个查询很慢? 这是我在读了几百篇之后阅读的顺序。 ## 一个要锚定的查询 ``sql
解释(分析、缓冲区)
SELECT u.id, u.email, count(o.id) AS order_count
来自用户 u
LEFT JOIN 命令 o ON o.user_id = u.id
WHERE u.signup_at > now() - 间隔“30 天”
按 u.id 分组;
典型的输出片段:
HashAggregate(成本=12345.67..23456.78行=10000宽度=48) (实际时间=412.331..480.219行=8742循环=1) - >哈希右连接(成本= 2345.67..11234.56行= 120000宽度= 44) (实际时间=18.221..380.115行=98213循环=1) … 缓冲区:共享命中=18234 读取=4521
整个查询(以毫秒为单位,针对该节点的一次执行)。 在示例中
上图:**480 毫秒**。 计划中的其他一切都崩溃了
那 480 毫秒过去了。 如果顶部的数字很好并且您正在追求慢速查询报告,
仔细检查您是否在应用程序中解释相同的查询
运行。 不同的参数值会产生不同的计划。 ## 步骤 2:比较 `rows=` 估计值与实际值 每行有两个行数: - `cost=…` 部分中的 `rows=N` — **规划者的估计**
- “实际时间=…”部分中的“rows=N”——**真正发生了什么** 当这些不一致达到 10 倍或更多时,规划器的操作就很糟糕
统计数据,并且*它上面的每个节点*都是使用错误的
假设。 这几乎总是你的错误。 在上面的例子中,计划者预计连接会产生 120,000
行,产生了 98,213 行。 没关系,大约有 20% 的折扣。 但如果我看到
类似于估计 100,实际 1,000,000 — 句号,这就是
问题。 常见原因: - 过时的统计信息 → 运行“ANALYZE the_table”并重新解释。 - 相关列 → 在列对上设置“CREATE STATISTICS”,或者 重写谓词。 - 计划者无法建模的倾斜数据 → 有时您需要通过以下方式进行提示 `pg_hint_plan` 或查询重写。 ## 步骤 3:找出时间实际去哪儿了 每个节点的“实际时间=A..B循环=L”读作:*“产生第一行
开始后 A 毫秒,最后一行在 B 毫秒生成; 该节点运行了 L 次。"* 要获取*仅此节点*(不包括子节点)花费的时间,您
必须减去孩子们的“实际时间”范围。 对于常见的
`loops=1` 的情况,简单的版本是: > **自拍时间 ≈ 该节点的“B” − 子节点“B”值的总和** 我自上而下地扫描计划,寻找具有最大自我的节点
时间。 这就是优化预算应该花的地方。 具有 `loops=N` 的节点,其中 N 很大(`Nested Loop` 内侧,对于
示例)报告每个循环时间。 乘以“循环”即可得到总数。 ## 步骤 4:使用 `BUFFERS` 告诉 CPU 的 I/O `EXPLAIN (ANALYZE, BUFFERS)` 添加如下行: ````
缓冲区:共享命中=18234 读取=4521
```` - `共享命中` — 页面已经在 Postgres 的缓冲区缓存中。 便宜的。 - `共享读取` — 从操作系统/磁盘获取的页面。 昂贵的。 - `temp write / read` — 排序或散列不适合 `work_mem` 并溢出到磁盘。 也很贵。 如果“读取”占主导地位,则计划很好,但数据不在缓存中。 运行
查询两次——第二次运行更能代表稳定
状态。 如果两次运行都因“read”较高而运行缓慢,则工作集不会
适合并且您需要更多 RAM、涉及更少页面的索引,或者
较小的查询。 如果您看到“temp write”出现在“Sort”或“Hash”节点上,请碰撞
该会话的“work_mem”并重新解释。 溢出到磁盘很容易
将节点的时间乘以 10×。 ## 我最常看到的三种模式 毕竟,实际的错误分为几种类型: **1. 对“应该索引”列进行顺序扫描。** 计划节点显示
`big_table Filter 上的 Seq Scan: (...)` 和 rows-removed-by-filter 是
巨大的。 在过滤器列上添加索引。 如果表是,则不要添加它
小或者过滤器是非选择性的; 策划者的选择是正确的。 **2. 需要哈希连接的嵌套循环。** 内侧运行
数千次。 几乎总是由行估计太低引起
上游(第 2 步问题)。 修复统计数据或重写谓词,然后
规划者选择正确的连接。 **3. 对应首先过滤的联接进行聚合。** 计划
连接所有内容然后过滤。 将过滤器推入子查询或
CTE,因此它在连接之前应用,减少连接的行数
来处理。 ## 快速阅读清单 当有人递给我“解释分析”时,我按顺序执行此操作: 1. 到达顶峰的总时间——真的很慢吗? 2. 任何节点的“行”估计值与实际值相差≥10×? ——这就是错误。 3. 哪个节点的自时间最大? ——这就是预算。 4. 是否有任何“临时写入”或异常高的“共享读取”? — 输入/输出问题。 5. 将慢速节点与上述三种模式之一相匹配。 就是这样。 这不是魔法,但顺序很重要——追逐最大的
检查行估计之前的自我时间可以让您优化
糟糕计划的*症状*而不是修复计划本身。 ## 相关文章 - [Python 上下文管理器:您实际需要的三种情况](/resources/ai-tools/python-context-managers-the- Three-cases-you-actually-need/) — Python 资源管理
- [Scrapling 回顾:更快、更隐蔽的 Python Scraping](/resources/dev-utils/scrapling-python-stealthy-web-scraping-review/) — 数据提取技术
- [Agent Reach:赋予您的 AI 代理互联网超能力](/resources/llm-frameworks/agent-reach-ai-agent-internet-access/) — AI 驱动的开发工具
💬 留言讨论