受支持版本: 当前版本 (18) / 17 / 16 / 15 / 14
开发版本: devel

EXPLAIN

EXPLAIN — 显示一个语句的执行计划

Synopsis

EXPLAIN [ ( option [, ...] ) ] statement

其中 option 可以是以下之一:

    ANALYZE [ boolean ]
    VERBOSE [ boolean ]
    COSTS [ boolean ]
    SETTINGS [ boolean ]
    GENERIC_PLAN [ boolean ]
    BUFFERS [ boolean ]
    SERIALIZE [ { NONE | TEXT | BINARY } ]
    WAL [ boolean ]
    TIMING [ boolean ]
    SUMMARY [ boolean ]
    MEMORY [ boolean ]
    FORMAT { TEXT | XML | JSON | YAML }

描述

这个命令显示PostgreSQL规划器为给定语句生成的执行计划。执行计划会显示将如何扫描该语句引用的表 — 例如普通顺序扫描、索引扫描等 —,如果引用了多个表,还会显示将使用哪些连接算法来汇集每个输入表中的所需行。

显示结果中最关键的部分是语句执行代价的估计值,它是规划器对运行该语句需要多长时间的猜测(以任意代价单位衡量,但按惯例表示磁盘页面抓取次数)。实际上会显示两个数字:返回第一行之前的启动代价,以及返回全部行的总代价。对大多数查询来说,总代价才是关键;但在某些场景中,例如EXISTS中的子查询,规划器会选择启动代价最小而不是总代价最小的计划(因为无论如何执行器都会在得到一行后停止)。此外,如果你用LIMIT子句限制返回的行数,规划器会在端点代价之间作适当插值,以估计哪个计划实际上最便宜。

ANALYZE选项会让该语句被实际执行,而不仅仅是生成计划。随后会把实际运行统计信息加入显示结果,包括每个计划节点中耗费的总时间(以毫秒计)以及它实际返回的总行数。这有助于判断规划器的估计是否接近实际情况。

Important

请记住,使用ANALYZE选项时,该语句会被实际执行。尽管EXPLAIN会丢弃SELECT本来会返回的任何输出,语句的其他副作用仍会照常发生。如果你希望对INSERTUPDATEDELETEMERGECREATE TABLE ASEXECUTE语句使用EXPLAIN ANALYZE,而又不让该命令影响你的数据,请采用这种方法:

BEGIN;
EXPLAIN ANALYZE ...;
ROLLBACK;

参数

ANALYZE #

执行该命令并显示实际运行时间及其他统计信息。此参数默认为FALSE

VERBOSE #

显示有关计划的附加信息。具体包括:计划树中每个节点的输出列列表、模式限定的表名和函数名、总是用其范围表别名标注表达式中的变量,以及总是打印显示统计信息的每个触发器名称。如果已经计算出查询标识符,也会将其显示出来,详见compute_query_id。此参数默认为FALSE

COSTS #

包含每个计划节点的估计启动代价和总代价,以及估计行数和每行的估计宽度。此参数默认为TRUE

SETTINGS #

包含配置参数信息。具体来说,会列出影响查询规划且其值不同于内置默认值的选项。此参数默认为FALSE

GENERIC_PLAN #

允许语句包含形如$1的参数占位符,并生成一个不依赖这些参数值的通用计划。关于通用计划以及哪些语句类型支持参数的细节,详见PREPARE。此参数不能与ANALYZE一起使用。此参数默认为FALSE

BUFFERS #

包含缓冲区使用信息。具体来说,会包括共享块命中、读取、写脏和写出的数量,本地块命中、读取、写脏和写出的数量,临时块读取和写出的数量,以及在启用track_io_timing时读取和写出数据文件块、本地块和临时文件块所花费的时间(以毫秒计)。hit表示该块在需要时已经在缓存中找到,因此避免了一次读取。共享块包含普通表和索引的数据;本地块包含临时表和索引的数据;临时块则包含排序、哈希、Materialize 计划节点等场景使用的短期工作数据。dirtied块数表示此查询修改的、先前未修改过的块数;written块数表示在查询处理期间该后端从缓存中逐出的、先前已被写脏的块数。某个上层节点显示的块数包含其所有子节点使用的块数。在文本格式中,只打印非零值。使用ANALYZE时会自动包含缓冲区信息。

SERIALIZE #

包含对查询输出数据进行序列化的代价信息,也就是把它转换成要发送给客户端的文本或二进制格式。如果数据类型的输出函数开销很大,或者必须从行外存储中取回TOAST化的值,这部分时间可能会占常规查询执行时间的很大一部分。EXPLAIN的默认行为SERIALIZE NONE不会执行这些转换。如果指定SERIALIZE TEXTSERIALIZE BINARY,则会执行相应的转换,并测量所耗时间(除非指定了TIMING OFF)。如果还指定了BUFFERS选项,则转换过程中涉及的缓冲区访问也会被计入。不过,无论如何EXPLAIN都不会真的把结果数据发送给客户端,因此无法用这种方式研究网络传输代价。只有在同时启用ANALYZE时才能启用序列化。如果写出SERIALIZE而不带参数,则假定为TEXT

WAL #

包含 WAL 记录生成的信息。具体来说,会包括记录数、完整页面映像(fpi)数量、以字节计的 WAL 生成量,以及 WAL 缓冲区变满的次数。在文本格式中,只打印非零值。此参数只能在同时启用ANALYZE时使用。此参数默认为FALSE

TIMING #

在输出中包含实际启动时间以及每个节点中耗费的时间。反复读取系统时钟的开销在某些系统上可能会显著拖慢查询,因此当只需要实际行数而不需要精确时间时,把此参数设置为FALSE可能会有用。即使通过这个选项关闭了节点级计时,整个语句的运行时间也总会被测量。此参数只能在同时启用ANALYZE时使用。此参数默认为TRUE

SUMMARY #

在查询计划之后包含摘要信息(例如汇总后的时间信息)。使用ANALYZE时默认包含摘要信息;否则默认不包含,但可以使用此选项启用。在EXPLAIN EXECUTE中,规划时间包括从缓存中取出计划所需的时间,以及在必要时重新规划所需的时间。

MEMORY #

包含查询规划阶段的内存消耗信息。具体来说,会包括规划器内存结构实际使用的精确存储量,以及把分配开销计算在内后的总内存量。此参数默认为FALSE

FORMAT #

指定输出格式,可以是 TEXT、XML、JSON 或 YAML。非文本输出包含与文本输出相同的信息,但更容易被程序解析。此参数默认为TEXT

boolean #

指定所选选项应开启还是关闭。可以写TRUEON1来启用选项,写FALSEOFF0来禁用它。boolean值也可以省略,在这种情况下假定其值为TRUE

statement #

任何你希望查看其执行计划的SELECTINSERTUPDATEDELETEMERGEVALUESEXECUTEDECLARECREATE TABLE ASCREATE MATERIALIZED VIEW AS语句。

输出

该命令的结果是为statement选择的计划的文本描述,并可选择附带执行统计信息。Section 14.1描述了所提供的信息。

注解

为了让PostgreSQL查询规划器在优化查询时能够做出相当有根据的决策,查询中用到的所有表的pg_statistic数据都应保持最新。通常autovacuum 守护进程会自动处理这一点。但如果某个表最近内容发生了大量变化,你可能需要手工执行一次ANALYZE,而不是等待 autovacuum 跟上这些变化。

为了度量执行计划中每个节点的运行时代价,当前的EXPLAIN ANALYZE实现会给查询执行增加性能分析开销。因此,对一个查询运行EXPLAIN ANALYZE有时会比正常执行该查询慢得多。开销大小取决于查询的性质以及所用平台。最坏情况出现在那些自身每次执行耗时极少的计划节点上,以及获取当前时间的操作系统调用相对较慢的机器上。

示例

要显示一个只有单个integer列且包含 10000 行的表上的简单查询计划:

EXPLAIN SELECT * FROM foo;

                       QUERY PLAN
---------------------------------------------------------
 Seq Scan on foo  (cost=0.00..155.00 rows=10000 width=4)
(1 row)

下面是同一查询,但使用 JSON 输出格式:

EXPLAIN (FORMAT JSON) SELECT * FROM foo;
           QUERY PLAN
--------------------------------
 [                             +
   {                           +
     "Plan": {                 +
       "Node Type": "Seq Scan",+
       "Relation Name": "foo", +
       "Alias": "foo",         +
       "Startup Cost": 0.00,   +
       "Total Cost": 155.00,   +
       "Plan Rows": 10000,     +
       "Plan Width": 4         +
     }                         +
   }                           +
 ]
(1 row)

如果存在索引,并且我们使用了带有可索引WHERE条件的查询,EXPLAIN可能会显示不同的计划:

EXPLAIN SELECT * FROM foo WHERE i = 4;

                         QUERY PLAN
--------------------------------------------------------------
 Index Scan using fi on foo  (cost=0.00..5.98 rows=1 width=4)
   Index Cond: (i = 4)
(2 rows)

下面是同一查询,但采用 YAML 格式:

EXPLAIN (FORMAT YAML) SELECT * FROM foo WHERE i='4';
          QUERY PLAN
-------------------------------
 - Plan:                      +
     Node Type: "Index Scan"  +
     Scan Direction: "Forward"+
     Index Name: "fi"         +
     Relation Name: "foo"     +
     Alias: "foo"             +
     Startup Cost: 0.00       +
     Total Cost: 5.98         +
     Plan Rows: 1             +
     Plan Width: 4            +
     Index Cond: "(i = 4)"
(1 row)

XML 格式留给读者自行练习。

下面是同一计划,但隐藏了代价估计:

EXPLAIN (COSTS FALSE) SELECT * FROM foo WHERE i = 4;

        QUERY PLAN
----------------------------
 Index Scan using fi on foo
   Index Cond: (i = 4)
(2 rows)

下面是使用聚合函数的查询计划示例:

EXPLAIN SELECT sum(i) FROM foo WHERE i < 10;

                             QUERY PLAN
-------------------------------------------------------------------​--
 Aggregate  (cost=23.93..23.93 rows=1 width=4)
   ->  Index Scan using fi on foo  (cost=0.00..23.92 rows=6 width=4)
         Index Cond: (i < 10)
(3 rows)

下面是使用EXPLAIN EXECUTE显示预备查询执行计划的示例:

PREPARE query(int, int) AS SELECT sum(bar) FROM test
    WHERE id > $1 AND id < $2
    GROUP BY foo;

EXPLAIN ANALYZE EXECUTE query(100, 200);

                                                       QUERY PLAN
-------------------------------------------------------------------​------------------------------------------------------
 HashAggregate  (cost=10.77..10.87 rows=10 width=12) (actual time=0.043..0.044 rows=10.00 loops=1)
   Group Key: foo
   Batches: 1  Memory Usage: 24kB
   Buffers: shared hit=4
   ->  Index Scan using test_pkey on test  (cost=0.29..10.27 rows=99 width=8) (actual time=0.009..0.025 rows=99.00 loops=1)
         Index Cond: ((id > 100) AND (id < 200))
         Index Searches: 1
         Buffers: shared hit=4
 Planning Time: 0.244 ms
 Execution Time: 0.073 ms
(10 rows)

当然,这里显示的具体数字取决于所涉及表的实际内容。还要注意,由于规划器的改进,这些数字甚至所选查询策略在PostgreSQL的不同版本之间都可能发生变化。此外,ANALYZE命令使用随机采样来估计数据统计信息;因此,即使表中数据的实际分布没有变化,在重新运行一次ANALYZE之后,代价估计也可能发生变化。

请注意,前一个示例展示了一个自定义计划,它针对的是EXECUTE中给定的特定参数值。我们也可能想查看参数化查询的通用计划,这可以用GENERIC_PLAN来完成:

EXPLAIN (GENERIC_PLAN)
  SELECT sum(bar) FROM test
    WHERE id > $1 AND id < $2
    GROUP BY foo;

                                  QUERY PLAN
-------------------------------------------------------------------​------------
 HashAggregate  (cost=26.79..26.89 rows=10 width=12)
   Group Key: foo
   ->  Index Scan using test_pkey on test  (cost=0.29..24.29 rows=500 width=8)
         Index Cond: ((id > $1) AND (id < $2))
(4 rows)

在这个例子中,解析器正确地推断出$1$2应与id具有相同的数据类型,因此缺少来自PREPARE的参数类型信息并不是问题。在其他情况下,可能需要显式指定参数符号的类型,这可以通过对它们进行类型转换来做到,例如:

EXPLAIN (GENERIC_PLAN)
  SELECT sum(bar) FROM test
    WHERE id > $1::integer AND id < $2::integer
    GROUP BY foo;

兼容性

SQL 标准中没有定义EXPLAIN语句。

以下语法在PostgreSQL 9.0 之前使用,并且仍然受支持:

EXPLAIN [ ANALYZE ] [ VERBOSE ] statement

注意,在这种语法中,选项必须严格按显示的顺序指定。

另见

ANALYZE

提交更正

如果您发现文档中有不正确的内容、与您使用特定功能的经验不符或需要进一步说明,请使用此表单来报告文档问题。