EXPLAIN — 显示语句的执行计划
EXPLAIN [ (option
[, ...] ) ]statement
whereoption
can be one of: 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 规划器为给定语句生成的执行计划。执行计划显示对语句引用的表将如何进行扫描——通过 plain sequential scan、index scan 等——以及如果引用了多张表,将使用什么连接算法让每个输入表中所需的 row 汇聚在一起。
显示内容中最关键的部分是预计语句执行成本,这是规划器对运行语句所需时间(以任意成本单位衡量,但传统意味着磁盘页面抓取)的猜测。实际上显示的数字有两个:第一行返还之前的前置成本,以及返还所有 row 的总成本。对于大多数查询来说,重要的是总成本,但在诸如 EXISTS
中的子查询等上下文中,规划器将选择最小的前置成本而不是最小的总成本(因为执行器将在一行之后停止)。此外,如果您使用 LIMIT
子句限制返回的行数,规划器会对端点成本进行适当插值,以估计哪种计划真正最便宜。
ANALYZE
选项导致实际执行语句,而不仅仅是规划。然后将实际的运行时间统计信息添加到显示中,包括每个计划节点内花费的总时间(以毫秒为单位)以及实际返回的总行数。这对于查看规划器的估值是否接近现实很有用。
请记住,在使用 ANALYZE
选项时实际执行语句。尽管 EXPLAIN
将放弃 SELECT
将返回的任何输出,但语句的其他副作用将照常进行。如果您希望对 INSERT
、UPDATE
、DELETE
、MERGE
、CREATE TABLE AS
或 EXECUTE
语句使用 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已启用。 命中表示已经避免了读取,因为需要时块已在缓存中找到。共享块包含来自常规表和索引的数据;本地块包含来自临时表和索引的数据,而临时块包含在排序、哈希、物化计划节点以及类似情况下使用的短期工作数据。 变脏的块数表示此查询更改的先前未修改的块数;而 写入的块数表示在查询处理期间这个后端从缓存中移除的以前变脏的块数。显示的上层节点的块数包括其所有子节点使用的块数。在文本格式中,仅打印非零值。此参数默认为FALSE
。
SERIALIZE
包括序列化查询输出数据的成本信息,即将其转换为文本或二进制格式以发送到客户端。如果数据类型输出函数很昂贵,或者如果TOASTed 值必须从脱机存储中获取。 EXPLAIN
的默认行为 SERIALIZE NONE
不执行这些转换。如果指定 SERIALIZE TEXT
或 SERIALIZE BINARY
,则执行适当的转换,并测量执行时间(除非指定 TIMING OFF
)。如果还指定 BUFFERS
选项,则转换中涉及的任何缓冲区访问也会被计数。但是,在任何情况下, EXPLAIN
实际上都不会将结果数据发送到客户端;因此无法通过这种方式来调查网络传输成本。仅当还启用 ANALYZE
时才可能启用序列化。如果编写 SERIALIZE
时不带参数,则假定为 TEXT
。
WAL
包括有关 WAL 记录生成的信息。具体而言,包括记录数、整页图像 (fpi) 数以及以字节生成的大小。在文本格式中,仅打印非零值。此参数仅可与启用 ANALYZE
时使用。它默认为 FALSE
。
TIMING
在输出中包括实际启动时间和在每个节点中花费的时间。在某些系统上,重复读取系统时钟会显著降低查询速度,因此当仅需要实际行计数而不是确切时间时,将此参数设置为 FALSE
可能很有用。始终测量整个语句的运行时间,即使使用此选项关闭了节点级计时。此参数仅可与启用 ANALYZE
时使用。它默认为 TRUE
。
SUMMARY
在查询计划后包括汇总信息(例如,汇总计时信息)。当使用 ANALYZE
时,默认情况下包含汇总信息,但通常不包含汇总信息,但是可以使用此选项启用汇总信息。 EXPLAIN EXECUTE
中的计划时间包括从高速缓存中获取计划所需的时间以及在必要时重新计划所需的时间。
MEMORY
包括有关查询计划阶段内存消耗的信息。具体而言,包括计划器内存中结构使用的精确存储量以及考虑分配开销的总内存。此参数默认为 FALSE
。
FORMAT
指定输出格式,可以是 TEXT、XML、JSON 或 YAML。非文本输出包含与文本输出格式相同的信息,但程序更容易解析。此参数默认为 TEXT
。
boolean
指定所选选项应打开还是关闭。你可以编写 TRUE
、ON
或 1
来启用该选项,并编写 FALSE
、OFF
或 0
来禁用该选项。boolean
值也可以省略,在这种情况下,假定为 TRUE
。
statement
希望查看其执行计划的任何 SELECT
、INSERT
、UPDATE
、DELETE
、MERGE
、VALUES
、EXECUTE
、DECLARE
、CREATE TABLE AS
或 CREATE MATERIALIZED VIEW AS
语句。
该命令的结果是对为 statement
选择的计划的文本描述,必要时附有执行统计。 第 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 loops=1) Group Key: foo Batches: 1 Memory Usage: 24kB -> Index Scan using test_pkey on test (cost=0.29..10.27 rows=99 width=8) (actual time=0.009..0.025 rows=99 loops=1) Index Cond: ((id > 100) AND (id < 200)) Planning Time: 0.244 ms Execution Time: 0.073 ms (7 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
请注意,在此语法中,必须严格按照显示的顺序指定选项。