精品欧美一区二区三区在线观看 _久久久久国色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技術棧公眾號

国产精品另类一区| 老司机精品福利视频| 欧美精品一区二区久久婷婷| 国自产拍偷拍精品啪啪一区二区| 无码国产精品高潮久久99| 蘑菇福利视频一区播放| 中文字幕日韩精品在线| 国产探花一区二区三区| 亚洲精品福利电影| 一区在线观看视频| 精品亚洲一区二区三区四区五区高| 日韩精品久久久久久免费| 91日韩在线| 亚洲精品成人久久电影| 污网站在线免费| 国产精品25p| 中文字幕日韩欧美一区二区三区| 国产一区二区三区免费不卡| 中国精品一区二区| 亚洲激情网址| 久热在线中文字幕色999舞| 亚洲精品中文字幕在线播放| 国产精品1区在线| 欧美日韩午夜视频在线观看| avove在线观看| 国产高清免费在线播放| 风间由美性色一区二区三区| 国产精品视频中文字幕91| 日韩免费视频网站| 五月久久久综合一区二区小说| 日韩精品在线视频美女| 国产亚洲精品成人a| 激情小说亚洲| 色欧美片视频在线观看在线视频| 97超碰国产精品| 久操视频在线免费播放| 国产人成亚洲第一网站在线播放| 91大片在线观看| 亚洲无码精品在线播放| 久久亚洲风情| 69**夜色精品国产69乱| 久久精品国产亚洲AV无码麻豆| 99视频精品视频高清免费| 亚洲精品之草原avav久久| 国产原创剧情av| 亚洲不卡视频| 91精品欧美福利在线观看| 欧美日韩一区二区三区69堂| 亚洲成人看片| 色欧美日韩亚洲| 北条麻妃av高潮尖叫在线观看| 亚洲精品mv| 色婷婷综合久久久久中文 | 成人三级视频在线观看一区二区| 一炮成瘾1v1高h| 老司机精品视频在线| 国产美女精彩久久| 91久久久久国产一区二区| 久久99精品国产麻豆不卡| 国产免费一区视频观看免费| 中文字幕一二三四| 麻豆成人久久精品二区三区红 | 国产乱码精品一区二区三区亚洲人| 欧美亚洲国产bt| 91av视频免费观看| 免费精品一区| 精品国产91洋老外米糕| 鲁大师私人影院在线观看| 欧美五码在线| 亚洲新声在线观看| 国产wwwwxxxx| 一区二区三区午夜视频| 欧美日韩高清在线观看| 日本三级网站在线观看| 麻豆91精品| 国产欧美精品xxxx另类| www.com在线观看| 成人三级在线视频| 欧美精品亚洲| 秋霞午夜理伦电影在线观看| 亚洲伊人色欲综合网| 久久久999免费视频| 亚洲承认视频| 日韩欧美国产综合一区| 91av在线免费| 日韩av在线中文字幕| 欧美另类极品videosbestfree| 日本亚洲色大成网站www久久| 久久久久在线| 91最新在线免费观看| 性xxxx搡xxxxx搡欧美| 国产欧美精品一区aⅴ影院| 青青草影院在线观看| 欧美三级网站| 91麻豆精品国产| 免费成人蒂法网站| 欧美激情欧美| 2020国产精品视频| 国产精品久久影视| 91麻豆成人久久精品二区三区| 在线国产伦理一区| av老司机在线观看| 欧美日韩成人综合在线一区二区| 美女搡bbb又爽又猛又黄www| 欧美色图一区| 久久免费高清视频| 亚洲最新av网站| 99精品视频在线观看| 久久免费看毛片| 日韩电影免费看| 日韩无一区二区| 天天操天天舔天天射| 激情欧美国产欧美| 国产日韩欧美综合| 天堂√在线中文官网在线| 亚洲欧美精品午睡沙发| 日韩肉感妇bbwbbwbbw| 美日韩黄色大片| 欧美猛交ⅹxxx乱大交视频| 久久久精品毛片| 91一区一区三区| 亚洲色婷婷久久精品av蜜桃| 岛国精品在线| 亚洲女成人图区| 日韩手机在线观看| 国产高清亚洲一区| 手机福利在线视频| aaaa欧美| 亚洲色在线视频| 亚洲黄色小说图片| 91在线观看污| 日韩a∨精品日韩在线观看| 欧美专区视频| 九九热99久久久国产盗摄| 一区二区www| 中文字幕 久热精品 视频在线 | 精品一区二区影视| 视频一区二区综合| 日韩精品免费观看视频| 亚洲人永久免费| 国产成人免费看| 91视频国产资源| 狠狠97人人婷婷五月| 国内自拍欧美| 2019亚洲日韩新视频| 亚洲日本香蕉视频| 欧美性猛交xxxx| 精品国产av无码| 久久精品人人| 少妇精品久久久久久久久久| 国产福利亚洲| 日韩一中文字幕| av中文字幕免费在线观看| 亚洲欧美日韩久久精品| 精品国产午夜福利在线观看| 综合激情一区| 国产精品免费一区二区三区观看| 国产丝袜在线播放| 亚洲国产精彩中文乱码av在线播放| 日本熟妇成熟毛茸茸| av一区二区久久| 国产亚洲精品网站| 国产伦精品一区二区三区视频| 国产高清在线不卡| 午夜视频在线| 日韩三级免费观看| 日本一区二区不卡在线| 久久亚洲一级片| 538任你躁在线精品免费| 欧美高清视频在线观看mv| 91免费视频国产| 91吃瓜在线观看| 国产亚洲美女久久| 国产精品久久久久毛片| 午夜婷婷国产麻豆精品| 中文字幕狠狠干| 麻豆精品精品国产自在97香蕉| 中文精品一区二区三区| 9l视频自拍蝌蚪9l视频成人| 欧亚精品在线观看| 欧美一区二区三区| 亚洲第一男人天堂| 小泽玛利亚一区二区三区视频| 中文字幕亚洲在| 日本少妇xxxx| 麻豆国产91在线播放| 99er在线视频| 欧美一级精品| 国产精品视频福利| 成人性片免费| 孩xxxx性bbbb欧美| 91网在线播放| 亚洲第一综合天堂另类专| 国产成人a v| 亚洲v中文字幕| 182在线观看视频| 99国产精品久| 黄色片免费网址| 久久精品日韩欧美| www.亚洲成人网| 欧美oldwomenvideos| 国模一区二区三区私拍视频| 国产精品伦一区二区| 性欧美办公室18xxxxhd| 男人天堂久久久| 亚洲成人999| 一级片在线观看视频| 红桃av永久久久| 东方av正在进入| 国产日韩精品一区二区浪潮av| av电影中文字幕| 久久超碰97中文字幕| 国产二区视频在线播放| 欧美国产专区| 一区二区三区四区五区视频| 秋霞在线一区| 国产传媒一区二区| 宅男噜噜噜66国产精品免费| 国产精品久久久久久av下载红粉| 国产福利在线免费观看| 欧美成人免费视频| 五月天婷婷在线视频| 亚洲欧美日韩中文视频| 日本精品999| 欧美v亚洲v综合ⅴ国产v| 中文字幕永久在线视频| 日本韩国精品一区二区在线观看| 国产精品成人国产乱| 一区二区三区中文字幕电影 | 亚洲综合免费观看高清完整版在线 | 国产精品福利av| 国产成人福利在线| 久久蜜桃一区二区| 人妻丰满熟妇av无码久久洗澡| 成人毛片在线观看| www.com日本| 成人丝袜18视频在线观看| www.四虎在线| 成人福利视频网站| 成人性生活免费看| 99久久婷婷国产综合精品电影| 麻豆av免费看| 成人午夜大片免费观看| 高清中文字幕mv的电影| 国产成人免费视| 中文字幕天堂av| 成人av网站大全| 黄色a一级视频| 久久人人97超碰com| 在线观看日本中文字幕| 国产亚洲一本大道中文在线| 最新中文字幕av| 国产欧美一区二区精品忘忧草 | 欧美高清不卡| av在线com| 亚洲激情二区| 成人一级片网站| 日韩国产欧美在线观看| 91小视频网站| 国产乱码精品一区二区三区忘忧草 | 欧美大片免费高清观看| 国产成人一区二区三区小说| a成人v在线| 91在线网站视频| 粉嫩久久久久久久极品| 蜜桃狠狠色伊人亚洲综合网站| 久久99视频| 四虎免费在线观看视频| 亚洲高清激情| 黄色片视频在线播放| 麻豆一区二区在线| 久久久无码人妻精品无码| gogogo免费视频观看亚洲一| 久操视频免费看| 中文字幕一区不卡| 欧美亚韩一区二区三区| 欧美主播一区二区三区美女| 国产精品欧美激情在线| 亚洲国产精品久久| www.av在线播放| 久久久久久中文| 在线国产成人影院| 3d动漫啪啪精品一区二区免费 | 国产成人亚洲精品狼色在线| 国产激情第一页| 亚洲欧洲三级电影| 亚洲黄色一区二区| 欧美日本不卡视频| 完全免费av在线播放| 99re6热在线精品视频播放速度| 精品伊人久久久| 亚洲综合欧美日韩| 亚洲精品四区| 超碰人人草人人| 91免费看视频| 国产高潮国产高潮久久久91 | 国产色片在线观看| 亚洲精品少妇网址| 69xxx在线| 日韩免费视频在线观看| 日韩在线亚洲| 亚洲一区精彩视频| 亚洲永久网站| 91精品国产高清91久久久久久 | 九九热精品视频在线| 在线综合+亚洲+欧美中文字幕| 五月婷婷丁香六月| 欧美巨乳在线观看| 成人久久网站| 欧美日韩国产高清视频| 亚洲特色特黄| 中文字幕亚洲影院| 国产人成一区二区三区影院| 日韩三级视频在线| 日韩精品一区二区三区视频播放 | 欧美日韩国产欧| 牛夜精品久久久久久久| 91美女蜜桃在线| 四虎成人精品永久免费av| 欧美一区二区精品在线| 91福利在线视频| 国产成人97精品免费看片| 精品精品精品| av动漫在线免费观看| 九九热在线视频观看这里只有精品| www.中文字幕av| 精品国产91久久久久久| 黄色av免费观看| 欧美疯狂做受xxxx高潮| 国产美女精品视频免费播放软件| 亚洲福利av在线| 日韩高清一级片| 亚洲一区二区三区日韩 | 国产精品久久久久久久久久久久久久久久久久 | 国产精品久久久久久久浪潮网站| 亚洲精品久久久久久国| 在线观看亚洲成人| 国产高清免费av在线| 日韩av男人的天堂| 亚洲伊人春色| 99久久国产宗和精品1上映| 久久久久99精品国产片| 精品国产xxx| 国产亚洲成精品久久| 欧美free嫩15| 亚洲午夜精品久久久中文影院av| 美女脱光内衣内裤视频久久影院| 成人激情五月天| 欧美日韩欧美一区二区| 日本中文字幕视频在线| 国产精品一区二区三区在线播放| 久久在线免费| 欧美人与性动交α欧美精品| 一区二区三区中文免费| 亚洲卡一卡二卡三| 国外成人性视频| 香蕉久久精品日日躁夜夜躁| 免费裸体美女网站| 国产精品色哟哟网站| 91精品视频免费在线观看| 蜜臀久久99精品久久久无需会员 | 91麻豆免费视频| 超碰在线97观看| 久久综合久久美利坚合众国| 日本免费精品| 黄色一级在线视频| 国产亚洲精品免费| 亚洲综合五月天婷婷丁香| 九九热这里只有精品6| 日本成人7777| 中文字幕第88页| 一区二区三区在线视频免费观看| 蜜桃视频在线观看www| 青青久久av北条麻妃海外网| 97精品国产| 国产黑丝一区二区| 在线看日本不卡| 伊人春色在线观看| 噜噜噜噜噜久久久久久91| 另类专区欧美蜜桃臀第一页| 国产一级av毛片| 国产亚洲一区二区精品| 日本在线一区二区三区| 成人综合视频在线| 亚洲欧洲日韩在线| 亚洲色图21p| 亚洲自拍偷拍第一页| 亚洲欧美日本日韩| 久久人妻无码aⅴ毛片a片app| 亚洲国产成人在线播放| jizz欧美| 夫妻免费无码v看片| 综合色中文字幕| 日韩大胆视频| 99精彩视频| 久久成人18免费观看| 欧美一区二区三区四| 久久国产精品久久久久久| 九九热线有精品视频99| 中文字幕永久免费|