精品欧美一区二区三区在线观看 _久久久久国色av免费观看性色_国产精品久久在线观看_亚洲第一综合网站_91精品又粗又猛又爽_小泽玛利亚一区二区免费_91亚洲精品国偷拍自产在线观看 _久久精品视频在线播放_美女精品久久久_欧美日韩国产成人在线

你確定讀懂了PostgreSQL執行計劃嗎?

數據庫 PostgreSQL
在執行任何 SQL 語句之前,PostgreSQL 優化器都會為它創建一個執行計劃(Query Plan)。執行計劃描述了 SQL 語句的具體實現步驟,例如使用全表掃描還是索引查找的方式獲取表中的數據,連接查詢使用 Nested Loop Join、Hash Join 還是 Sort Merge Join 算法,以及連接的順序等等。

在執行任何 SQL 語句之前,PostgreSQL 優化器都會為它創建一個執行計劃(Query Plan)。執行計劃描述了 SQL 語句的具體實現步驟,例如使用全表掃描還是索引查找的方式獲取表中的數據,連接查詢使用 Nested Loop Join、Hash Join 還是 Sort Merge Join 算法,以及連接的順序等等。

當我們遇到慢查詢等性能問題時,通常可以先查看 SQL 語句的執行計劃,因此本文給大家詳細介紹一下如何獲取并解讀 PostgreSQL 執行計劃。

獲取執行計劃

PostgreSQL 提供了 EXPLAIN 語句,可以很方便地獲取 SQL 語句的執行計劃。EXPLAIN 語句的基本語法如下:

EXPLAIN statement;

我們首先創建初始化數據:

CREATE TABLE test(
  id INTEGER PRIMARY KEY,
  vc VARCHAR(100),
  vn NUMERIC,
  vd DATE,
  other char(100) DEFAULT 'N/A' NOT NULL
);

INSERT INTO test (id, vc, vn, vd)
SELECT id, 's'||random(), 100*random(),'2024-01-01'::date+(100*random())::int 
FROM GENERATE_SERIES(1, 10000) id;

ANALYZE test;

最后的 ANALYZE 命令是為了收集表的統計信息,幫助查詢優化器做出合理的選擇。

提示:PostgreSQL 優化器需要知道最新的數據庫統計信息(pg_statistic)才能選擇合適的執行計劃,通常 autovacuum 后臺守護進程會定期更新統計信息。但是,如果某個表近期執行了大量數據更新,我們可以執行 ANALYZE 命令更新它的統計信息。

以下是一個簡單的 EXPLAIN 示例:

EXPLAIN SELECT * FROM test;

QUERY PLAN                                                |
----------------------------------------------------------+
Seq Scan on test  (cost=0.00..323.00 rows=10000 width=141)|

PostgreSQL 執行計劃結果包含幾部分內容:操作(Seq Scan on test)、成本(cost)、預估返回的行數(rows)以及預估每行數據的平均寬度(width),單位為字節。

其中,最重要的信息是成本,它的單位一般是磁盤頁讀取次數。成本包含兩個數字,分別代表返回第一行數據之前的啟動成本和返回全部結果的總成本。對于大多數查詢而言,我們需要關注總成本;但是某些情況下(例如 EXISTS 子查詢),查詢計劃器會選擇最小的啟動成本,因為執行器只需要獲取一行數據。另外,如果我們使用了 LIMIT 子句限制返回的行數,查詢計劃器會基于兩個成本計算一個合適的中間值。

EXPLAIN 語句還支持一些選項,其中需要重點注意的一個選項就是 ANALYZE,因為它不僅顯示預估的執行計劃,還會實際執行相應的語句并且返回執行時間統計。例如:

EXPLAIN ANALYZE
SELECT * FROM test;

QUERY PLAN                                                                                              |
--------------------------------------------------------------------------------------------------------+
Seq Scan on test  (cost=0.00..323.00 rows=10000 width=141) (actual time=0.021..1.374 rows=10000 loops=1)|
Planning Time: 0.083 ms                                                                                 |
Execution Time: 1.890 ms                                                                                |

可以看出,執行計劃結果中增加了實際運行時間(actual time)統計,包括每個操作節點消耗的時間(毫秒)、返回的數據行數以及執行的次數。Planning Time 是生成執行計劃的時間;Execution Time 是執行語句的實際時間,不包括 Planning Time。ANALYZE 選項通常可以用于檢查查詢計劃器的評估是否準確。

雖然 ANALYZE 選項忽略了 SELECT 語句返回的結果,但是對于 INSERT、UPDATE、DELETE 等語句,它仍然會修改表中的數據,為了避免這種副作用,我們可以在事務內部獲取執行計劃,然后回滾事務:

BEGIN;
EXPLAIN ANALYZE ...;
ROLLBACK;

其他 EXPLAIN 選項的介紹可以參考下文。

解讀執行計劃

PostgreSQL 執行計劃的結構是一棵由計劃節點組成的樹,EXPLAIN 命令的每一行對應一個節點。

每一行節點除了匯總信息之外,還可能包含縮進行,顯示了完成該節點的底層操作。節點的執行順序按照縮進來判斷,縮進越多的越先執行,同樣縮進的從上至下執行。第一行顯示了預估的總成本,它也是優化器最小化的目標。

執行計劃最底層的節點是掃描節點,通常用于從表中返回原始數據。我們就從簡單的單表訪問開始。

單表訪問

對于不同的表訪問方法,存在以下不同的掃描節點:

  • 順序掃描(適用于返回大部分數據行)
  • 索引掃描(適用于返回很少數據行)
  • 位圖索引掃描(適用于返回較多數據行)

順序掃描就是全表掃描,它會依次讀取整個表中的數據。如果查詢條件字段沒有索引,一般需要執行順序掃描,例如:

EXPLAIN 
SELECT *
FROM test
WHERE vd = '2024-01-01'::date;

QUERY PLAN                                             |
-------------------------------------------------------+
Seq Scan on test  (cost=0.00..348.00 rows=59 width=141)|
  Filter: (vd = '2024-01-01'::date)                    |

順序掃描對應的操作名稱為 Seq Scan,通常意味著我們需要基于查詢條件字段創建索引,從而通過索引優化查詢。

索引掃描意味著遍歷索引的 B-樹葉子節點,找到所有滿足條件的索引項,然后通過索引指針讀取表中的數據。例如:

EXPLAIN 
SELECT *
FROM test
WHERE id = 1000;

QUERY PLAN                                                            |
----------------------------------------------------------------------+
Index Scan using test_pkey on test  (cost=0.29..8.30 rows=1 width=141)|
  Index Cond: (id = 1000)                                             |

如果我們需要查詢的字段都可以通過索引獲取,PostgreSQL 可以使用僅索引掃描(Index-Only Scan)技術優化查詢。例如:

CREATE INDEX idx_test_vn ON test(vn,id);

EXPLAIN 
SELECT vn, id
FROM test
WHERE vn = 1000;

QUERY PLAN                                                                  |
----------------------------------------------------------------------------+
Index Only Scan using idx_test_vn on test  (cost=0.29..4.30 rows=1 width=16)|
  Index Cond: (vn = '1000'::numeric)                                        |

索引 idx_test_vn 包含了 vn 字段和 id 字段,查詢語句不需要訪問表中的數據即可返回查詢結果。

提示:PostgreSQL 提供了覆蓋索引(Covering Index),可以進一步實現 Index-Only Scan 優化。另外,Index-Only Scan 優化需要滿足一個條件:MVCC 可見性,因為索引中并沒有存儲數據的可見性信息,只有表的元組中存儲了該信息。

索引掃描每次找到一個滿足條件的索引項時,都會基于元組指針再次訪問表中的數據(回表),這是一種隨機 IO。如果索引掃描只返回很少的數據行,它是一個很好的訪問方法。但是如果掃描索引返回的數據行比較多,大量的隨機回表會導致性能下降;一個優化的方法就是把回表的隨機 IO 變成順序 IO,為此 PostgreSQL 引入了位圖索引掃描。

位圖索引掃描(Bitmap Index Scan)的原理是一次掃描索引獲取所有滿足條件的元組指針,然后在內存中基于“位圖”數據結構進行排序,最后按照元組指針的物理順序訪問表(Bitmap Heap Scan)中的數據。例如:

CREATE INDEX idx_test_vd ON test(vd);

EXPLAIN 
SELECT *
FROM test
WHERE vd = '2024-01-01'::date;

QUERY PLAN                                                               |
-------------------------------------------------------------------------+
Bitmap Heap Scan on test  (cost=4.75..139.99 rows=60 width=141)          |
  Recheck Cond: (vd = '2024-01-01'::date)                                |
  ->  Bitmap Index Scan on idx_test_vd  (cost=0.00..4.74 rows=60 width=0)|
        Index Cond: (vd = '2024-01-01'::date)                            |

該查詢語句返回 60 行數據,使用索引掃描的話,還需要 60 次回表。因此,PostgreSQL 選擇了位圖索引的訪問方法。

Recheck Cond 發生在回表階段,因為如果基于元組構建位圖導致位圖過大,就會基于數據頁(Page)構建位圖(有損方式),也就是只記錄了哪些數據頁包含了所需的數據行,所以在讀取數據頁之后需要再次檢查具體的元組。對于無損方式構建的位圖,也會出現 Recheck Cond 節點,但是并不執行檢查操作。

位圖索引掃描更常見的一種情況是查詢條件組合使用了多個索引時,例如:

EXPLAIN 
SELECT *
FROM test
WHERE vn = 1000 OR vd = '2024-01-01'::date;

QUERY PLAN                                                                     |
-------------------------------------------------------------------------------+
Bitmap Heap Scan on test  (cost=9.06..146.25 rows=61 width=141)                |
  Recheck Cond: ((vn = '1000'::numeric) OR (vd = '2024-01-01'::date))          |
  ->  BitmapOr  (cost=9.06..9.06 rows=61 width=0)                              |
        ->  Bitmap Index Scan on idx_test_vn  (cost=0.00..4.29 rows=1 width=0) |
              Index Cond: (vn = '1000'::numeric)                               |
        ->  Bitmap Index Scan on idx_test_vd  (cost=0.00..4.74 rows=60 width=0)|
              Index Cond: (vd = '2024-01-01'::date)                            |

查詢首先基于 idx_test_vn 以及 idx_test_vd 進行了位圖索引掃描,然后進行了位圖合并(BitmapOr),最后基于位圖結果進行回表。

位圖索引掃描存在一個副作用,就是查詢結果不再按照索引順序返回,無法通過索引優化 ORDER BY。例如:

EXPLAIN
SELECT *
FROM test
WHERE vd BETWEEN '2024-01-01'::date AND '2024-01-31'::date
ORDER BY vd;

QUERY PLAN                                                                           |
-------------------------------------------------------------------------------------+
Sort  (cost=485.23..492.65 rows=2966 width=141)                                      |
  Sort Key: vd                                                                       |
  ->  Bitmap Heap Scan on test  (cost=46.69..314.18 rows=2966 width=141)             |
        Recheck Cond: ((vd >= '2024-01-01'::date) AND (vd <= '2024-01-31'::date))    |
        ->  Bitmap Index Scan on idx_test_vd  (cost=0.00..45.94 rows=2966 width=0)   |
              Index Cond: ((vd >= '2024-01-01'::date) AND (vd <= '2024-01-31'::date))|

查詢計劃中增加了額外的排序節點(Sort)。

提示:位圖索引掃描增加了內存和 CPU 的消耗,但是會減少磁盤 IO。

除了表之外,還有一些特殊的數據源(例如 VALUES 子句和 FROM 子句中的集合函數)擁有特殊的掃描類型。例如:

EXPLAIN
SELECT *
FROM (VALUES(1,'sql')) t(id,v);

QUERY PLAN                               |
-----------------------------------------+
Result  (cost=0.00..0.01 rows=1 width=36)|

EXPLAIN
SELECT *
FROM pg_catalog.generate_series(1, 100);

QUERY PLAN                                                          |
--------------------------------------------------------------------+
Function Scan on generate_series  (cost=0.00..1.00 rows=100 width=4)|

多表連接

如果查詢涉及多表連接操作,執行計劃中的掃描節點之上將會顯示額外的 Join 節點。通常連接操作一次連接兩個表,如果查詢包含多個連接操作,按照順序進行連接,前兩個表連接的中間結果和下一個表進行連接。

PostgreSQL 實現了以下三種連接算法:

  • 嵌套循環(Nested Loop)
  • 哈希連接(Hash Join)
  • 排序合并(Merge Join)

嵌套循環連接類似于編程語言中的嵌套 for 循環,首先從外部表(驅動表)中獲取滿足條件的數據,然后為每一行數據遍歷一次內部表(被驅動表),獲取所有匹配的數據。下圖演示了嵌套循環連接的執行過程:

以下查詢將 test 和它自己進行交叉連接:

EXPLAIN
SELECT *
FROM test t1
CROSS JOIN test t2;

QUERY PLAN                                                               |
-------------------------------------------------------------------------+
Nested Loop  (cost=0.00..1250671.00 rows=100000000 width=282)            |
  ->  Seq Scan on test t1  (cost=0.00..323.00 rows=10000 width=141)      |
  ->  Materialize  (cost=0.00..373.00 rows=10000 width=141)              |
        ->  Seq Scan on test t2  (cost=0.00..323.00 rows=10000 width=141)|

PostgreSQL 選擇了嵌套循環算法實現以上連接查詢,其中 Materialize 說明 t2 的掃描結果進行了緩存,極大地減少了磁盤訪問次數。

哈希連接使用其中一個表中滿足條件的記錄創建哈希表,然后掃描另一個表進行匹配。哈希連接的執行過程如下圖所示:

以下查詢仍然使用 test 進行自連接,但是指定了連接條件

EXPLAIN
SELECT *
FROM test t1
JOIN test t2 ON t1.vc=t2.vc;

QUERY PLAN                                                               |
-------------------------------------------------------------------------+
Hash Join  (cost=448.00..908.50 rows=10000 width=282)                    |
  Hash Cond: ((t1.vc)::text = (t2.vc)::text)                             |
  ->  Seq Scan on test t1  (cost=0.00..323.00 rows=10000 width=141)      |
  ->  Hash  (cost=323.00..323.00 rows=10000 width=141)                   |
        ->  Seq Scan on test t2  (cost=0.00..323.00 rows=10000 width=141)|

PostgreSQL 選擇了哈希連接算法實現以上連接查詢,并且使用 t2 表的數據創建哈希表。

排序合并連接先將兩個數據源按照連接字段進行排序(Sort),然后合并兩個已經排序的集合,返回滿足連接條件的結果。排序合并連接的執行過程如下圖所示:

以下查詢使用主鍵 id 字段進行連接,并且按照 t1 的主鍵進行排序:

EXPLAIN
SELECT *
FROM test t1
JOIN test t2 ON t1.id=t2.id
ORDER BY t1.id;

QUERY PLAN                                                                           |
-------------------------------------------------------------------------------------+
Merge Join  (cost=0.57..1142.57 rows=10000 width=282)                                |
  Merge Cond: (t1.id = t2.id)                                                        |
  ->  Index Scan using test_pkey on test t1  (cost=0.29..496.29 rows=10000 width=141)|
  ->  Index Scan using test_pkey on test t2  (cost=0.29..496.29 rows=10000 width=141)|

PostgreSQL 選擇了排序合并連接算法實現以上連接查詢,它可以避免額外的排序操作。

集合運算

集合運算符(UNION、INTERSECT、EXCEPT)用于將多個查詢語句的結果進行并集、交集、差集運算,它們也會在執行計劃中顯示單獨的節點。例如:

EXPLAIN 
SELECT * 
FROM test t1
UNION ALL
SELECT *
FROM test t2;

QUERY PLAN                                                         |
-------------------------------------------------------------------+
Append  (cost=0.00..746.00 rows=20000 width=141)                   |
  ->  Seq Scan on test t1  (cost=0.00..323.00 rows=10000 width=141)|
  ->  Seq Scan on test t2  (cost=0.00..323.00 rows=10000 width=141)|

其中,Append 節點意味著將兩個查詢的結果追加合并成一個結果。

以下是一個 INTERSECT 示例:

EXPLAIN
SELECT * 
FROM test t1
INTERSECT   
SELECT *
FROM test t2;

QUERY PLAN                                                                                              |
--------------------------------------------------------------------------------------------------------+
SetOp Intersect  (cost=8324.77..8624.77 rows=10000 width=666)                                           |
  ->  Sort  (cost=8324.77..8374.77 rows=20000 width=666)                                                |
        Sort Key: "*SELECT* 1".id, "*SELECT* 1".vc, "*SELECT* 1".vn, "*SELECT* 1".vd, "*SELECT* 1".other|
        ->  Append  (cost=0.00..946.00 rows=20000 width=666)                                            |
              ->  Subquery Scan on "*SELECT* 1"  (cost=0.00..423.00 rows=10000 width=145)               |
                    ->  Seq Scan on test t1  (cost=0.00..323.00 rows=10000 width=141)                   |
              ->  Subquery Scan on "*SELECT* 2"  (cost=0.00..423.00 rows=10000 width=145)               |
                    ->  Seq Scan on test t2  (cost=0.00..323.00 rows=10000 width=141)                   |

其中,SetOp Intersect 節點代表了并集運算,它由一個 Append 節點和 Sort 節點組成,因為 INTERSECT 運算符需要去除重復記錄。

最后是一個 EXCEPT 示例:

EXPLAIN
SELECT * 
FROM test t1
EXCEPT    
SELECT *
FROM test t2;

QUERY PLAN                                                                                              |
--------------------------------------------------------------------------------------------------------+
SetOp Except  (cost=8324.77..8624.77 rows=10000 width=666)                                              |
  ->  Sort  (cost=8324.77..8374.77 rows=20000 width=666)                                                |
        Sort Key: "*SELECT* 1".id, "*SELECT* 1".vc, "*SELECT* 1".vn, "*SELECT* 1".vd, "*SELECT* 1".other|
        ->  Append  (cost=0.00..946.00 rows=20000 width=666)                                            |
              ->  Subquery Scan on "*SELECT* 1"  (cost=0.00..423.00 rows=10000 width=145)               |
                    ->  Seq Scan on test t1  (cost=0.00..323.00 rows=10000 width=141)                   |
              ->  Subquery Scan on "*SELECT* 2"  (cost=0.00..423.00 rows=10000 width=145)               |
                    ->  Seq Scan on test t2  (cost=0.00..323.00 rows=10000 width=141)                   |

其中,SetOp Except 節點表示差集運算,同樣由一個 Append 節點和 Sort 節點組成。

排序分組

排序(ORDER BY)和分組(GROUP BY)也是查詢語句中常見的操作,它們都有專門的節點類型。例如:

EXPLAIN
SELECT *
FROM test 
ORDER BY vd;

QUERY PLAN                                                      |
----------------------------------------------------------------+
Sort  (cost=987.39..1012.39 rows=10000 width=141)               |
  Sort Key: vd                                                  |
  ->  Seq Scan on test  (cost=0.00..323.00 rows=10000 width=141)|

雖然 vd 字段存在索引,但是查詢需要返回全部數據,PostgreSQL 還是選擇了全表掃描加排序(Sort)的方式。

如果索引能夠同時完成數據過濾(WHERE)和排序,執行計劃中就不會出現 Sort 節點。例如:

EXPLAIN
SELECT *
FROM test 
WHERE vn = 1000
ORDER BY id;

QUERY PLAN                                                              |
------------------------------------------------------------------------+
Index Scan using idx_test_vn on test  (cost=0.29..8.30 rows=1 width=141)|
  Index Cond: (vn = '1000'::numeric)                                    |

索引 idx_test_vn 包含了 vn 以及 id 字段。

PostgreSQL 實現了兩種分組算法:哈希聚合算法以及排序聚合算法。

哈希聚合算法使用一個臨時哈希表對數據進行分組聚合,完成數據哈希之后的結果就是分組結果。例如:

EXPLAIN
SELECT vc,count(*)
FROM test 
GROUP BY vc;

QUERY PLAN                                                     |
---------------------------------------------------------------+
HashAggregate  (cost=373.00..473.00 rows=10000 width=28)       |
  Group Key: vc                                                |
  ->  Seq Scan on test  (cost=0.00..323.00 rows=10000 width=20)|

vc 字段沒有索引,PostgreSQL 選擇了哈希聚合算法(HashAggregate)。

排序聚合算法首先將數據按照分組字段進行排序,將每個組內的數據都排列到一起,然后進行聚合操作。例如:

EXPLAIN
SELECT vc,count(*)
FROM test 
GROUP BY vc
ORDER BY vc;

QUERY PLAN                                                           |
---------------------------------------------------------------------+
GroupAggregate  (cost=987.39..1162.39 rows=10000 width=28)           |
  Group Key: vc                                                      |
  ->  Sort  (cost=987.39..1012.39 rows=10000 width=20)               |
        Sort Key: vc                                                 |
        ->  Seq Scan on test  (cost=0.00..323.00 rows=10000 width=20)|

考慮到查詢結果還需要進行排序,PostgreSQL 選擇了排序聚合算法(Sort + GroupAggregate)。

排序聚合算法還可以基于索引避免排序操作,例如:

EXPLAIN
SELECT vn,count(*)
FROM test 
GROUP BY vn
ORDER BY vn;

QUERY PLAN                                                                              |
----------------------------------------------------------------------------------------+
GroupAggregate  (cost=0.29..504.29 rows=10000 width=20)                                 |
  Group Key: vn                                                                         |
  ->  Index Only Scan using idx_test_vn on test  (cost=0.29..354.29 rows=10000 width=12)|

vn 字段存在索引,因此執行計劃中只有 GroupAggregate 節點,而沒有 Sort 節點。

限制結果

Top-N 查詢和分頁查詢通常只需要返回有限數量的結果,例如:

EXPLAIN ANALYZE
SELECT *
FROM test 
ORDER BY vn 
FETCH FIRST 5 ROWS ONLY;

QUERY PLAN                                                                                                                     |
-------------------------------------------------------------------------------------------------------------------------------+
Limit  (cost=0.29..0.91 rows=5 width=141) (actual time=0.013..0.017 rows=5 loops=1)                                            |
  ->  Index Scan using idx_test_vn on test  (cost=0.29..1246.20 rows=10000 width=141) (actual time=0.012..0.015 rows=5 loops=1)|
Planning Time: 0.084 ms                                                                                                        |
Execution Time: 0.030 ms                                                                                                       |

執行計劃中的 Limit 節點表示 PostgreSQL 在獲取足夠數據行之后停止底層操作,索引掃描(Index Scan)不僅避免了排序操作,而且只需要掃描 5 個索引條目(actual time=0.012…0.015 rows=5 loops=1)就可以終止掃描,這種優化技術被稱為管道(pipelined)操作。

Limit 操作的性能依賴于底層操作的實現,如果底層執行的是非管道操作,例如:

EXPLAIN ANALYZE 
SELECT *
FROM test 
ORDER BY vc 
FETCH FIRST 5 ROWS ONLY;

QUERY PLAN                                                                                                          |
--------------------------------------------------------------------------------------------------------------------+
Limit  (cost=489.10..489.11 rows=5 width=141) (actual time=3.361..3.362 rows=5 loops=1)                             |
  ->  Sort  (cost=489.10..514.10 rows=10000 width=141) (actual time=3.360..3.360 rows=5 loops=1)                    |
        Sort Key: vc                                                                                                |
        Sort Method: top-N heapsort  Memory: 27kB                                                                   |
        ->  Seq Scan on test  (cost=0.00..323.00 rows=10000 width=141) (actual time=0.015..0.529 rows=10000 loops=1)|
Planning Time: 0.124 ms                                                                                             |
Execution Time: 3.384 ms                                                                                            |

vc 字段沒有索引,所以需要執行額外的排序(Sort)。排序可能導致明顯的性能問題,因為 Limit 節點需要等待所有數據(actual time=0.015…0.529 rows=10000 loops=1)完成排序之后才能返回數據。

訪問謂詞與過濾謂詞

對于 WHERE 子句(謂詞),PostgreSQL 提供了三種不同的實現方法:

  • 索引訪問謂詞
  • 索引過濾謂詞
  • 表級過濾謂詞

索引訪問謂詞(Index Access Predicate)指定了索引葉子節點遍歷的開始和結束條件。例如:

EXPLAIN ANALYZE 
SELECT *
FROM test 
WHERE id BETWEEN 100 AND 120;

QUERY PLAN                                                                                                        |
------------------------------------------------------------------------------------------------------------------+
Index Scan using test_pkey on test  (cost=0.29..8.71 rows=21 width=141) (actual time=0.007..0.011 rows=21 loops=1)|
  Index Cond: ((id >= 100) AND (id <= 120))                                                                       |
Planning Time: 0.133 ms                                                                                           |
Execution Time: 0.024 ms                                                                                          |

其中,Index Cond 表示索引掃描時基于該條件開始和結束,所以它屬于訪問謂詞。

索引過濾謂詞(Index Filter Predicate)在遍歷索引葉子節點時用于判斷是否返回該索引項,但是不會用于判斷遍歷的開始和結束條件,也就不會縮小索引掃描的范圍。例如:

CREATE INDEX idx_test_vdvc ON test(vd, vc);

EXPLAIN ANALYZE 
SELECT *
FROM test 
WHERE vd BETWEEN '2024-01-01'::date AND '2024-01-02'::date AND vc = 'xxx';

QUERY PLAN                                                                                                          |
--------------------------------------------------------------------------------------------------------------------+
Index Scan using idx_test_vdvc on test  (cost=0.29..9.36 rows=1 width=141) (actual time=0.024..0.024 rows=0 loops=1)|
  Index Cond: ((vd >= '2024-01-01'::date) AND (vd <= '2024-01-02'::date) AND ((vc)::text = 'xxx'::text))            |
Planning Time: 0.124 ms                                                                                             |
Execution Time: 0.040 ms                                                                                            |

idx_test_vdvc 索引基于 vd 和 vc 兩個字段,但是查詢條件中只有 vd 用于決定索引遍歷的開始條件和結束條件,vc 字段只能用于判斷是否返回該索引項。因為 vd 是范圍條件,導致索引節點中的 vc 字段不再具體順序性。PostgreSQL 執行計劃沒有區分索引訪問謂詞和索引過濾謂詞,統一顯示為 Index Cond。

注意:索引過濾謂詞看似使用了索引,但是隨著數據量的增長可能導致性能明顯下降,因為它沒有減少索引掃描的范圍。

對于以上查詢語句,如果我們創建 idx_test_vdvc 索引時把 vc 字段放在最前面,就可以充分利用索引優化性能,因為此時所有的謂詞都是所有訪問謂詞。

表級過濾謂詞(Table Level Filter Predicate)是指謂詞中的非索引字段在表級別進行判斷,這意味著數據庫需要讀取表中的數據然后再應用該條件。例如:

EXPLAIN ANALYZE 
SELECT *
FROM test 
WHERE id = 100 AND other = 'N/A';

QUERY PLAN                                                                                                      |
----------------------------------------------------------------------------------------------------------------+
Index Scan using test_pkey on test  (cost=0.29..8.30 rows=1 width=141) (actual time=0.019..0.020 rows=1 loops=1)|
  Index Cond: (id = 100)                                                                                        |
  Filter: (other = 'N/A'::bpchar)                                                                               |
Planning Time: 0.103 ms                                                                                         |
Execution Time: 0.037 ms                                                                                        |

查詢使用了主鍵索引掃描(Index Scan),其中 id 是索引訪問謂詞(Index Cond),other 是表級過濾謂詞(Filter)。

提示:一般來說,對于相同的查詢語句,訪問謂詞的性能好于索引過濾謂詞,索引過濾謂詞的性能好于表級過濾謂詞。

輸出參數

最后我們介紹一下 EXPLAIN 語句的完整語法:

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

其中 option 可以為以下選項之一:

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

其中,ANALYZE 和 VERBOSE 選項支持兩種指定方法;其他選項需要使用括號包含,多個選項使用逗號進行分隔。

statement 可以是以下語句之一:SELECT、INSERT、UPDATE、DELETE、MERGE、VALUES、EXECUTE、DECLARE、CREATE TABLE AS、CREATE MATERIALIZED VIEW AS。

boolean 用于啟用或者禁用相關選項。TRUE、ON 或者 1 表示啟用,FALSE、OFF 或者 0 表示禁用。如果忽略了 boolean 設置,默認為啟用。

ANALYZE

ANALYZE 選項不僅顯示預估的執行計劃,還會實際執行相應的語句,并且返回執行時間和其他信息統計。該選項默認為 FALSE。

一方面,為了測量執行計劃中每個節點的執行時成本,當前 EXPLAIN ANALYZE 的實現在執行計劃中增加了一些分析開銷,因此執行 EXPLAIN ANALYZE 命令有時候會導致查詢比正常運行花費的時間明顯更長。具體的分析開銷取決于查詢語句以及數據庫運行的平臺,有可能查詢節點每次執行只需要很短的時間,但是操作系統獲取時間的調用反而更慢,可以使用 pg_test_timing 工具測量系統的計時開銷。

另一方面, EXPLAIN ANALYZE 不需要將查詢結果發送到客戶端,因此沒有包含網絡傳輸和轉換成本。

VERBOSE

VERBOSE 選項用于在執行計劃中顯示額外的信息。例如:

EXPLAIN VERBOSE 
SELECT *
FROM test;

QUERY PLAN                                                        |
------------------------------------------------------------------+
Seq Scan on emerald.test  (cost=0.00..323.00 rows=10000 width=141)|
  Output: id, vc, vn, vd, other                                   |

以上 EXPLAIN VERBOSE 顯示了順序掃描節點輸出的字段列表(Output),以及包含模式名限定的表名(emerald.test)。

對于不同的操作節點,VERBOSE 選項還會顯示其他額外信息。該選項默認禁用。

COSTS

COSTS 選項用于輸出每個計劃節點的預估啟動成本和總成本,以及預估行數和平均長度。該選項默認啟用。例如:

EXPLAIN (COSTS)
SELECT *
FROM test;

QUERY PLAN                                                |
----------------------------------------------------------+
Seq Scan on test  (cost=0.00..323.00 rows=10000 width=141)|

SETTINGS

SETTINGS 選項用于顯示配置參數,尤其是影響查詢計劃的非默認設置的參數。該選項默認禁用。例如:

EXPLAIN (SETTINGS)
SELECT *
FROM test;

QUERY PLAN                                                |
----------------------------------------------------------+
Seq Scan on test  (cost=0.00..323.00 rows=10000 width=141)|
Settings: search_path = 'hrdb, public, "$user"'           |

GENERIC_PLAN

PostgreSQL 16 版本增加了 GENERIC_PLAN 選項,可以為預編譯語句 生成通用執行計劃,這種執行計劃不依賴于綁定變量(例如 $1、$2等)的值。例如:

EXPLAIN (GENERIC_PLAN)
SELECT *
FROM test
WHERE vn = $1;

QUERY PLAN                                                              |
------------------------------------------------------------------------+
Index Scan using idx_test_vn on test  (cost=0.29..8.30 rows=1 width=141)|
  Index Cond: (vn = $1)                                                 |

GENERIC_PLAN 選項默認禁用,而且不能和 ANALYZE 選項一起使用,因為 ANALYZE 需要執行語句。

另外,預編譯語句也可能使用定制執行計劃,也就是使用綁定變量的具體值創建執行計劃。例如:

PREPARE query_test(numeric)
AS 
SELECT *
FROM test
WHERE vn = $1;

EXPLAIN EXECUTE query_test(10);

QUERY PLAN                                                              |
------------------------------------------------------------------------+
Index Scan using idx_test_vn on test  (cost=0.29..8.30 rows=1 width=141)|
  Index Cond: (vn = '10'::numeric)                                                 |


DEALLOCATE query_test;

索引掃描的訪問謂詞中使用了具體的參數值(10)。

提示:運行時參數 plan_cache_mode 決定了預編譯語句使用通用執行計劃還是定制執行計劃。

BUFFERS

BUFFERS 選項用于顯示緩沖區使用情況,默認禁用。例如:

EXPLAIN (ANALYZE, BUFFERS)
SELECT *
FROM test
WHERE id = 1000;

QUERY PLAN                                                                                                      |
----------------------------------------------------------------------------------------------------------------+
Index Scan using test_pkey on test  (cost=0.29..8.30 rows=1 width=141) (actual time=0.030..0.032 rows=1 loops=1)|
  Index Cond: (id = 1000)                                                                                       |
  Buffers: shared hit=3                                                                                         |
Planning Time: 0.266 ms                                                                                         |
Execution Time: 0.071 ms                                                                                        |

其中,shared hit 表示共享塊命中。

具體來說,BUFFERS 選項顯示的信息包括共享內存塊命中(hit)、讀取(read)、標記臟塊(dirtied)以及寫入(written)數量,本地內存塊命中(hit)、讀取(read)、標記臟塊(dirtied)以及寫入(written)數量,臨時內存塊的讀取(read)和寫入(written)數量。如果啟用了服務器參數 track_io_timing ,還會顯示讀寫數據文件塊和臨時文件塊的時間(毫秒)。

其中,一次命中意味著避免了一次磁盤讀取,因為所需數據塊已經存在緩存中。共享內存塊包含了普通表和索引的緩存數據,本地內存塊包含了臨時表和索引的緩存數據;臨時內存塊包含了排序、哈希、物化節點等操作使用的臨時數據。

臟塊的數量表示之前未改動,但是當前查詢修改的數據塊;寫入塊的數量表示之前被標記為臟塊,同時在當前查詢處理過程總被后臺進程刷新到磁盤的數據塊。上層節點顯示的數量包含了子節點的數量,對于 TEXT 輸出格式,只顯示非零數據值。

WAL

WAL 選項用于顯示有關預寫式日志記錄生成的信息。具體來說,包括記錄數、全頁鏡像數(fpi)以及生成的 WAL(字節)。如果 FORMAT 選項的值為 TEXT(默認值),只顯示非零信息。該選項只能在啟用 ANALYZE 選項時使用,默認為禁用。

TIMING

TIMING 選項用于顯示每個計劃節點的啟用時間和完成時間(毫秒),該選項只能在啟用 ANALYZE 選項時使用,默認為啟用。

某些平臺上重復讀取系統時間可能會明顯影響查詢性能,如果只關注實際返回的行數,可以在啟用 ANALYZE 選項時將該選項禁用。即使關閉了節點的計時功能,整個語句的運行時間仍然會統計并顯示。

SUMMARY

SUMMARY 選項用于在執行計劃之后顯示匯總信息(例如總的時間消耗)。如果啟用了 ANALYZE 選項,默認顯示匯總信息;否則默認不會顯示匯總信息。

對于 EXPLAIN EXECUTE 語句,Planning time 包含了從緩存中獲取執行計劃或者重新計劃消耗的時間。

FORMAT

FORMAT 選項用于指定執行計劃的輸出格式,可以使用 TEXT、XML、JSON 或者 YAML 格式。默認輸出格式為 TEXT,其他格式輸出的內容和 TEXT 格式相同,只是更方便程序處理。例如:

EXPLAIN (FORMAT JSON)
SELECT *
FROM test;

[
  {
    "Plan": {
      "Node Type": "Seq Scan",
      "Parallel Aware": false,
      "Async Capable": false,
      "Relation Name": "test",
      "Alias": "test",
      "Startup Cost": 0.00,
      "Total Cost": 323.00,
      "Plan Rows": 10000,
      "Plan Width": 141
    }
  }
]


責任編輯:華軒 來源: SQL編程思想
相關推薦

2022-02-15 07:36:21

SQLEXPLAIN數據庫

2021-04-24 12:01:08

MySQL數據庫Mysql執行計劃

2011-09-14 17:03:17

數據庫執行計劃解析

2015-04-22 14:17:45

SQL SERVERMSSQL SERVE緩沖區

2021-03-17 09:35:51

MySQL數據庫explain

2023-09-21 10:55:51

MysqlSQL語句

2021-05-28 10:46:36

MySQL執行計劃

2009-11-13 16:28:02

Oracle生成執行計

2024-09-12 15:16:14

2022-08-08 08:03:44

MySQL數據庫CBO

2022-08-15 15:09:26

SQL數據庫MySQL

2021-09-07 10:43:25

EverDB分布式執行

2009-11-10 16:00:05

Oracle執行計劃

2010-04-16 09:27:18

Ocacle執行計劃

2021-02-20 08:40:19

HiveExplain底層

2020-09-15 08:44:57

MySQL慢日志SQL

2009-11-18 17:05:47

捕獲Oracle SQ

2020-10-16 09:40:18

順序Spring AOPHTTP

2022-12-13 08:36:42

D-SMARTOracle數據庫

2022-11-02 15:28:55

MySQL執行計劃B+樹
點贊
收藏

51CTO技術棧公眾號

中文字幕av一区二区三区佐山爱| 欧美一级免费片| 成人在线免费视频观看| 777午夜精品视频在线播放| 看一级黄色录像| 熟妇人妻一区二区三区四区| 石原莉奈一区二区三区在线观看| xvideos亚洲人网站| 日本一级大毛片a一| 欧洲亚洲两性| 一区二区三区日韩欧美精品 | 香港三级日本三级a视频| 午夜国产在线观看| 国产一区二区三区久久悠悠色av| 97色在线视频| 国语对白在线播放| 精品日本12videosex| 精品国产乱码久久久久久闺蜜| 成人中文字幕av| 1234区中文字幕在线观看| 久久精品亚洲精品国产欧美kt∨ | 国产精品视频免费| 国产亚洲一区二区三区在线播放| 中文字幕人妻一区二区在线视频 | 亚洲男人天堂av网| 青青成人在线| 少妇人妻偷人精品一区二区| 精品一区二区三区影院在线午夜 | 成人性生活视频免费看| 国产二区在线播放| 99精品久久免费看蜜臀剧情介绍| 亚洲自拍欧美另类| 97成人在线观看| 日韩电影在线看| 色综合色综合网色综合| 希岛爱理中文字幕| 四虎成人av| 在线观看日韩视频| 久久丫精品忘忧草西安产品| 色婷婷精品视频| 亚洲国产成人爱av在线播放| 亚洲天堂小视频| 亚洲欧美在线综合| 欧美亚洲国产怡红院影院| 各处沟厕大尺度偷拍女厕嘘嘘| 日本乱理伦在线| 亚洲三级在线免费| 男女h黄动漫啪啪无遮挡软件| jizz视频在线观看| 欧美高清在线一区二区| 欧美久久在线| 欧美中文在线| 国产日产精品1区| 欧美日韩精品一区| 国产有码在线| 中文字幕乱码日本亚洲一区二区| 日本亚洲欧洲精品| a天堂中文在线88| 亚洲丝袜美腿综合| 中文字幕在线中文| 成人爽a毛片免费啪啪动漫| 一区二区免费视频| www精品久久| 日本午夜大片a在线观看| 欧美日韩中文在线观看| 日本精品www| 91成人在线| 欧美日韩国产影片| 97超碰人人看| 国产精品久av福利在线观看| 日韩av中文字幕在线播放| 久久久精品人妻无码专区| 欧美欧美黄在线二区| 在线视频欧美性高潮| 久久久精品少妇| 欧美日本亚洲韩国国产| 91爱爱小视频k| 无码人妻精品一区二区50| 麻豆高清免费国产一区| 96久久精品| 深夜福利视频在线免费观看| 国产午夜一区二区三区| 精品一区二区成人免费视频| 美女尤物在线视频| 日本精品视频一区二区| 天天干天天色天天干| 91欧美日韩在线| 亚洲图片在线综合| 国产十六处破外女视频| 美女诱惑黄网站一区| 成人免费淫片aa视频免费| 亚洲av综合色区无码一区爱av| 99久久精品费精品国产一区二区| 亚欧精品在线| 17videosex性欧美| 欧美日韩一级视频| 亚洲视频在线播放免费| 日韩激情免费| 91av国产在线| 99国产精品一区二区三区| 91免费版在线| 潘金莲一级淫片aaaaa免费看| 精品众筹模特私拍视频| 欧美三级中文字| xxxxxx黄色| 亚洲精品99| 国产精品福利网站| 全国男人的天堂网| 自拍偷拍亚洲欧美日韩| 草草久久久无码国产专区| 国产日本亚洲| 中日韩午夜理伦电影免费| 日韩欧美激情视频| 国产曰批免费观看久久久| 欧美黑人3p| 51漫画成人app入口| 51精品秘密在线观看| 精品人妻无码一区二区三区换脸| 亚洲调教视频在线观看| 91精品久久久久久久久久入口| 涩爱av在线播放一区二区| 有码一区二区三区| 婷婷激情5月天| 水蜜桃精品av一区二区| 日韩免费在线看| 天天综合在线视频| 亚洲国产中文字幕在线视频综合| 日本中文字幕二区| 日韩黄色大片| 国产精品久久久久久一区二区| 无码国产色欲xxxx视频| 亚洲国产综合色| 中国特级黄色片| 一二三区不卡| 国产精品欧美激情在线播放| 理论视频在线| 亚洲人成在线播放网站岛国| 99草草国产熟女视频在线| 三级精品视频| 91av在线播放视频| 色一情一乱一区二区三区| 亚洲国产欧美在线| 久久免费精品国产| 国产精品magnet| 国产91aaa| 阿v视频在线观看| 亚洲精品白浆高清久久久久久| 天堂资源在线播放| www.视频一区| 日韩精品一区二区三区久久| 首页亚洲中字| 日本在线精品视频| 高清美女视频一区| 欧美美女视频在线观看| 日本中文在线视频| 国产成人精品免费网站| 加勒比成人在线| 少妇久久久久| 国产精品18久久久久久麻辣| 成人高清免费在线播放| 欧美日韩三级在线| 91精品少妇一区二区三区蜜桃臀| 国产一区二区三区免费播放| 欧美成人精品免费| 九九精品久久| 国产欧美在线播放| 伊人222成人综合网| 欧美精品一区视频| 亚洲成人第一网站| 中文字幕一区二区三区视频| 古装做爰无遮挡三级聊斋艳谭| 欧美午夜电影在线观看| 久久精品日产第一区二区三区| 成人av观看| 色噜噜狠狠狠综合曰曰曰88av| 国产精品嫩草影院桃色| 午夜不卡av在线| 无码人妻aⅴ一区二区三区69岛| 久久精品国产亚洲高清剧情介绍 | 综合 欧美 亚洲日本| 国产精品自拍在线| 成人毛片视频网站| 99精品美女| 黄色91av| 涩涩涩久久久成人精品| 国模精品系列视频| 69久久精品| 亚洲第一福利网| 伊人22222| 精品久久久中文| 国产中文字幕久久| 本田岬高潮一区二区三区| 性欧美videossex精品| 韩国亚洲精品| 亚洲自拍三区| 日韩精品导航| 91久久精品国产91久久| 中文字幕成在线观看| 久久久国产精彩视频美女艺术照福利| 日本免费一区视频| 欧美日本一区二区| 91video| 亚洲美女偷拍久久| 夫妇露脸对白88av| 91网站视频在线观看| 精品人妻一区二区三区免费| 天堂av在线一区| 日本在线xxx| 一区二区三区四区电影| 午夜欧美性电影| 亚洲人成网亚洲欧洲无码| www.久久艹| 99视频这里有精品| 国产精品亚洲欧美导航| 在线免费日韩片| 久久久久久有精品国产| 国产精品va在线观看视色| 一区二区成人精品| 亚洲AV成人无码一二三区在线| 欧美一级夜夜爽| 在线观看国产小视频| 色婷婷久久久亚洲一区二区三区| 精品少妇theporn| 亚洲狼人国产精品| 国产天堂av在线| 中文字幕第一区| 婷婷色一区二区三区| 不卡一区二区中文字幕| 亚洲精品鲁一鲁一区二区三区| 狠狠色狠狠色合久久伊人| 欧美日韩怡红院| 日韩国产欧美一区二区三区| av7777777| 亚洲精品精选| 水蜜桃色314在线观看| 黄色日韩在线| 成人午夜视频免费观看| 欧美在线看片| 色一情一乱一乱一区91| 一区二区三区在线| 天天干天天色天天爽| 91精品天堂福利在线观看| 制服丝袜综合日韩欧美| 亚洲a在线视频| 看一级黄色录像| 亚洲欧美文学| 国产av人人夜夜澡人人爽麻豆| 国模一区二区三区| 妺妺窝人体色777777| 亚洲精品日韩久久| 欧美日韩在线中文| 日日骚欧美日韩| 我看黄色一级片| 九九在线精品视频| 下面一进一出好爽视频| 国产iv一区二区三区| 久久久久99人妻一区二区三区| 国产精品亚洲午夜一区二区三区| 色男人天堂av| 99这里只有精品| 国精产品一区一区三区免费视频| 91老师国产黑色丝袜在线| 新91视频在线观看| 国产精品久久久久久久久快鸭| 国产性生活大片| 午夜久久久久久久久久一区二区| 中国一级免费毛片| 欧美色综合网站| 国产av一区二区三区| 亚洲成人xxx| 国产午夜在线观看| 美女少妇精品视频| а√天堂资源官网在线资源 | 97久久精品一区二区三区的观看方式| 国产主播在线一区| 风间由美一区二区av101| 欧美黑人3p| 一区二区三区中文| 黑人糟蹋人妻hd中文字幕| 麻豆91精品视频| 亚洲综合中文网| 久久午夜羞羞影院免费观看| 日本黄色网址大全| 久久精子c满五个校花| 中日韩一级黄色片| 亚洲成人免费视频| 这里只有精品999| 精品成人一区二区| 天堂中文а√在线| 国模gogo一区二区大胆私拍 | 麻豆av一区二区| 91精品国产调教在线观看| 欧美日韩性生活片| 久久99精品一区二区三区三区| 欧美熟妇精品一区二区蜜桃视频| 国产视频一区在线播放| 国产一级特黄a高潮片| 欧美在线制服丝袜| 日韩中文字幕免费观看| 日韩一区二区三区国产| 二区三区不卡| 成人av片网址| 无码一区二区三区视频| 777米奇影视第四色| 丰满白嫩尤物一区二区| 少妇太紧太爽又黄又硬又爽小说| 亚洲香肠在线观看| 一级二级三级视频| 亚洲人成网站在线播| 久草在线视频福利| 91久久在线播放| 欧美一区电影| 美女av免费在线观看| 国产成人免费在线视频| 久久久久麻豆v国产| 色国产精品一区在线观看| 天天操天天射天天| 久99久在线视频| 亚洲欧洲专区| 性高潮久久久久久久久| 久久久久久久尹人综合网亚洲| 99免费观看视频| 怡红院av一区二区三区| 91国产精品一区| 视频直播国产精品| 88xx成人免费观看视频库| 久久精品国产美女| 999亚洲国产精| 中文字幕18页| 亚洲精品国产高清久久伦理二区| 艳妇乳肉豪妇荡乳av| 亚洲欧美另类自拍| 樱桃视频成人在线观看| 久久久国产精品一区二区三区| 亚洲人成在线影院| 亚洲观看黄色网| 婷婷丁香久久五月婷婷| 涩涩视频免费看| 97av视频在线| 亚洲97av| 国产三级三级三级看三级| 久久女同互慰一区二区三区| 日本韩国欧美中文字幕| 亚洲乱码av中文一区二区| 日本在线啊啊| 青青草久久网络| 久久激情五月激情| 中文字幕五月天| 精品久久五月天| 国产三级电影在线播放| 久久精品一二三区| 丝袜诱惑制服诱惑色一区在线观看 | 91女人18毛片水多国产| 久久不射电影网| 亚洲日本va午夜在线电影| 精品无码一区二区三区爱欲| 91在线小视频| 黄色av一区二区| 久久精品国产96久久久香蕉| 日韩欧美中文在线观看| av在线观看地址| 91年精品国产| 一区二区三区在线免费观看视频| 中文字幕一精品亚洲无线一区| av资源亚洲| 亚洲伊人婷婷| 国产91精品免费| 国产午夜精品久久久久| 伊人久久久久久久久久| 国产精品一级在线观看| 男人添女人荫蒂免费视频| 91免费看视频| 一区二区www| 久久人人爽人人| 精品国产一区二区三区香蕉沈先生 | 国产午夜精品免费一区二区三区| 欧美韩国日本| 国产一二三区在线播放| 久久天堂av综合合色蜜桃网| 亚洲一区二区天堂| 欧美精品久久久久| 久久最新网址| 亚洲无在线观看| 欧美视频中文字幕在线| 日韩黄色影院| 国产经品一区二区| 久久综合九色| 久久久久97国产| 国产亚洲欧洲高清| 亚洲成人影音| 一区二区xxx| 天天色天天爱天天射综合| 色综合久久影院| 精品午夜一区二区三区| 国产一区二区免费视频| 国内自拍视频在线播放| 久热99视频在线观看| 深爱激情久久| 在线xxxxx| 91麻豆精品国产91久久久使用方法 |