PostgreSQL 18 中文参考文档(未翻译部分保留英文)🌐 切换到英文原版 →

14.1. 使用 EXPLAIN #

14.1.1. EXPLAIN 基础
14.1.2. EXPLAIN ANALYZE
14.1.3. 注意事项与偏差场景

PostgreSQL 能够为提交的任意查询生成高效的查询计划(Query Plan)。选择正确的执行路径直接决定了查询耗时是一毫秒还是数小时。使用 EXPLAIN 命令可以完整检视优化器为特定查询制定的物理算子执行拓扑结构。

14.1.1. EXPLAIN 基础 #

查询计划的呈现形式是一棵由计划节点构成的物理算子树。最底层的叶子节点负责扫描数据表,上层节点负责执行关联过滤、内存排序或数据聚合等复杂操作。

最简单的查询计划展示了对单表的扫描操作:

EXPLAIN SELECT * FROM tenk1;

                         QUERY PLAN
-------------------------------------------------------------
 Seq Scan on tenk1  (cost=0.00..458.00 rows=10000 width=244)

在每个节点括号内部输出的核心度量指标含义如下:

  • cost=0.00..458.00:包含两部分预估代价数值。前置数值(0.00)是启动代价(Startup Cost),代表该节点输出第一行有效数据前所需的开销;后置数值(458.00)是总代价(Total Cost),代表该节点完全执行完毕返回全部结果集的总预计物理开销。代价以任意磁盘页面顺序读取开销为基准单位(通常约定 1.0 代表单次顺序磁盘页抓取)。

  • rows=10000:优化器预估该计划节点最终向外吐出的数据行总数。

  • width=244:优化器预估该节点产出的每一行记录的平均物理字节宽度。

当查询带有过滤条件时:

EXPLAIN SELECT * FROM tenk1 WHERE unique1 < 7000;

                         QUERY PLAN
------------------------------------------------------------
 Seq Scan on tenk1  (cost=0.00..470.00 rows=7000 width=244)
   Filter: (unique1 < 7000)

计划显示在顺序扫描算子上挂载了一个 “Filter” 过滤谓词,表明扫描算子会对读入的每一行逐一核验该条件,仅向上传递满足条件的行,因而预估输出行数收缩为 7000。

若过滤条件极具区分度且存在索引:

EXPLAIN SELECT * FROM tenk1 WHERE unique1 < 100;

                                  QUERY PLAN
-------------------------------------------------------------------​-----------
 Bitmap Heap Scan on tenk1  (cost=5.06..224.98 rows=100 width=244)
   Recheck Cond: (unique1 < 100)
   ->  Bitmap Index Scan on tenk1_unique1  (cost=0.00..5.04 rows=100 width=0)
         Index Cond: (unique1 < 100)

此时优化器智能切换为两阶段位图扫描:底层的子节点 Bitmap Index Scan 率先在索引中检索满足条件的行位置并生成内存位图;上层的父节点 Bitmap Heap Scan 则根据该位图按物理磁盘块顺序直接定位并抓取目标数据行,大幅减少随机 I/O,总代价显著低于全表顺序扫描。

14.1.2. EXPLAIN ANALYZE #

通过附加 ANALYZE 选项,EXPLAIN 会在实际物理执行该查询的同时,精确度量并打印出每个计划节点的实际运行毫秒耗时以及实际处理的真实数据行数:

EXPLAIN ANALYZE SELECT * FROM tenk1 WHERE unique1 < 100;

                                                           QUERY PLAN
-------------------------------------------------------------------​---------------------------------------------------------
 Bitmap Heap Scan on tenk1  (cost=5.06..224.98 rows=100 width=244) (actual time=0.046..0.198 rows=100 loops=1)
   Recheck Cond: (unique1 < 100)
   Exact Heap Blocks: 89
   ->  Bitmap Index Scan on tenk1_unique1  (cost=0.00..5.04 rows=100 width=0) (actual time=0.026..0.026 rows=100 loops=1)
         Index Cond: (unique1 < 100)
 Planning Time: 0.134 ms
 Execution Time: 0.239 ms
(7 rows)

对比 rows=100(预估行数)与 actual rows=100(真实物理返回行数),是诊断慢查询的核心抓手。若两者出现数个数量级的剧烈偏差,通常意味着系统统计信息严重失真或陈旧,导致优化器做出了错误的连接或扫描决策。

附加 BUFFERS 选项(如 EXPLAIN (ANALYZE, BUFFERS))还能进一步打印出每个算子在执行时直接命中的共享内存缓冲区页数(Hit)、从磁盘实际读取的物理块数(Read),以及向磁盘写出的脏块数(Written),帮助精准定位 I/O 瓶颈。

Caution

请牢记:EXPLAIN ANALYZE 会真实物理执行该查询!如果将其作用于 INSERT、UPDATE 或 DELETE 语句,数据将被真实写入或删除!若在测试时只想观察分析写操作的执行特征而不希望真正改变数据,请将其包裹在事务块内部并在最后显式执行 ROLLBACK:

BEGIN;
EXPLAIN ANALYZE DELETE FROM foo;
ROLLBACK;

14.1.3. 注意事项与偏差场景 #

EXPLAIN ANALYZE 测量的运行耗时在以下两种主要场景中会与常规真实执行存在偏差。首先,由于没有物理行发送给客户端,网络传输耗时不包含在内;除非指定 SERIALIZE,否则数据转换为文本格式的 I/O 开销也不包含在内。其次,EXPLAIN ANALYZE 本身附加的精细计时开销相当可观,尤其在操作系统 gettimeofday() 系统调用较慢的平台上更为显著。可以使用 pg_test_timing 工具测试当前操作系统的计时开销。

切勿将 EXPLAIN 在小型开发环境测试表上的分析结论盲目外推到海量生产表。优化器的成本预估是非线性的,数据表规模发生变化时规划器会采用完全不同的计划。典型例子是仅占单个磁盘页面的微型表,无论是否存在索引,优化器几乎总是选择顺序扫描,因为单次读页即可完成处理,索引检索反而产生多余 I/O。

有些场景下预估值与实际值虽有明显差异但并非错误。最典型的场景是带有 LIMIT 的查询:

EXPLAIN ANALYZE SELECT * FROM tenk1 WHERE unique1 < 100 AND unique2 > 9000 LIMIT 2;

                                                          QUERY PLAN
-------------------------------------------------------------------​------------------------------------------------------------
 Limit  (cost=0.29..14.33 rows=2 width=244) (actual time=0.051..0.071 rows=2.00 loops=1)
   Buffers: shared hit=16
   ->  Index Scan using tenk1_unique2 on tenk1  (cost=0.29..70.50 rows=10 width=244) (actual time=0.051..0.070 rows=2.00 loops=1)
         Index Cond: (unique2 > 9000)
         Filter: (unique1 < 100)
         Rows Removed by Filter: 287
         Index Searches: 1
         Buffers: shared hit=16
 Planning Time: 0.077 ms
 Execution Time: 0.086 ms

底层 Index Scan 节点的预估代价和行数是按完整执行计算的,但上层的 Limit 节点在获取 2 行后立即停止请求,因而实际处理行数仅为 2 行且耗时远低于总代价预估。这只是展示机制的差异,并非估算缺陷。

归并连接(Merge Join)也存在类似统计假象:当一个输入端耗尽且下一键值大于另一端最后键值时连接立即提前终止;另外外侧输入端若存在重复键值,内侧输入端会被回滚重扫,导致报告的内侧实际行数显著大于内表物理真实行数。此外,由于底层实现机制,BitmapAnd 与 BitmapOr 节点的实际行数计数恒显示为零。在扫描分区表时,执行器若在运行时判断某些子分区无需执行,会在输出中省略对应节点并标记 Subplans Removed: N。