2025年9月25日: PostgreSQL 18 釋出!
支援的版本: 當前 (18) / 17 / 16 / 15 / 14 / 13
開發版本: devel
不支援的版本: 12 / 11 / 10 / 9.6 / 9.5 / 9.4 / 9.3 / 9.2 / 9.1 / 9.0 / 8.4 / 8.3 / 8.2 / 8.1 / 8.0 / 7.4 / 7.3 / 7.2 / 7.1

EXPLAIN

EXPLAIN — 顯示語句的執行計劃

概要

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

where option 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 規劃器為給定語句生成的執行計劃。執行計劃顯示語句引用的表將如何被掃描——透過普通順序掃描、索引掃描等——如果引用了多個表,將使用什麼連線演算法來整合來自每個輸入表的必需行。

顯示中最關鍵的部分是估計的語句執行成本,這是規劃器對執行該語句所需時間的猜測(以任意的成本單位衡量,但通常表示磁碟頁面讀取)。實際上顯示了兩個數字:返回第一行之前的啟動成本,以及返回所有行的總成本。對於大多數查詢,總成本才是重要的,但在像 EXISTS 中的子查詢這樣的上下文中,規劃器會選擇最小的啟動成本而不是最小的總成本(因為執行器將在獲取一行後停止)。此外,如果您使用 LIMIT 子句限制要返回的行數,規劃器會在端點成本之間進行適當的插值,以估計哪個計劃才是真正最便宜的。

使用 ANALYZE 選項會實際執行該語句,而不僅僅是計劃。然後,執行時間統計資訊將被新增到顯示中,包括每個計劃節點實際花費的總時間(以毫秒為單位)以及它實際返回的總行數。這有助於瞭解規劃器的估計是否接近實際情況。

重要提示

請記住,使用 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

包括有關緩衝區使用情況的資訊。具體來說,包括命中(hit)、讀取(read)、髒(dirtied)和寫入(written)的共享塊數量,命中、讀取、髒和寫入的本地塊數量,讀取和寫入的臨時塊數量,以及讀取和寫入資料檔案塊、本地塊和臨時檔案塊所花費的時間(以毫秒為單位),前提是 track_io_timing 已啟用。 命中 意味著在需要時由於塊已在快取中而避免了讀取。共享塊包含來自常規表和索引的資料;本地塊包含來自臨時表和索引的資料;而臨時塊包含在排序、雜湊、物化計劃節點等情況下使用的短期工作資料。 塊的數量表示在此查詢中被更改的先前未修改的塊的數量;而 寫入 塊的數量表示在此後端在查詢處理過程中從快取中逐出的先前已髒的塊的數量。上層節點顯示的塊數包括其所有子節點使用的塊數。在文字格式中,只打印非零值。當使用 ANALYZE 時,會自動包含緩衝區資訊。

SERIALIZE

包括有關將查詢的輸出資料 序列化 的成本資訊,即將其轉換為文字或二進位制格式以傳送到客戶端。如果資料型別輸出函式開銷很大,或者如果TOASTed 值必須從行外儲存中獲取,這可能是常規查詢執行過程中花費時間的重要部分。 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 選擇的計劃的文字描述,可選擇性地附帶執行統計資訊。第 14.1 節 描述了提供的資訊。

註釋

為了讓 PostgreSQL 查詢規劃器在最佳化查詢時做出合理的決策,查詢中使用的所有表的 pg_statistic 資料應是最新的。通常 autovacuum 守護程序 會自動處理此問題。但是,如果一個表的 DDL 最近有substantial 變化,您可能需要執行手動 ANALYZE,而不是等待 autovacuum 跟上這些 DDL。

為了測量執行計劃中每個節點的執行成本,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 中給定的特定引數值的“custom”計劃。我們也可能希望看到引數化查詢的通用計劃,這可以透過 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

提交更正

如果您在文件中發現任何不正確、與您對特定功能的體驗不符或需要進一步說明的內容,請使用 此表單 來報告文件問題。