多變數相關性可以透過一個非常簡單的資料集來演示——一個只有兩列的表,這兩列都包含相同的值。
CREATE TABLE t (a INT, b INT); INSERT INTO t SELECT i % 100, i % 100 FROM generate_series(1, 10000) s(i); ANALYZE t;
如 第 14.2 節中所述,規劃器可以從 pg_class
中獲取頁數和行數來確定 t
的基數。
SELECT relpages, reltuples FROM pg_class WHERE relname = 't'; relpages | reltuples ----------+----------- 45 | 10000
資料分佈非常簡單;每列只有 100 個不同的值,並且均勻分佈。
以下示例顯示了對 a
列應用 WHERE
條件的估算結果:
EXPLAIN (ANALYZE, TIMING OFF, BUFFERS OFF) SELECT * FROM t WHERE a = 1; QUERY PLAN ------------------------------------------------------------------------------- Seq Scan on t (cost=0.00..170.00 rows=100 width=8) (actual rows=100.00 loops=1) Filter: (a = 1) Rows Removed by Filter: 9900
規劃器檢查該條件,並將此子句的選擇性確定為 1%。透過比較此估算值和實際行數,我們發現估算值非常準確(實際上是精確的,因為表非常小)。將 WHERE
條件更改為使用 b
列,會生成相同的計劃。但是,如果我們透過 AND
將相同的條件應用於兩個列,請觀察會發生什麼:
EXPLAIN (ANALYZE, TIMING OFF, BUFFERS OFF) SELECT * FROM t WHERE a = 1 AND b = 1; QUERY PLAN ----------------------------------------------------------------------------- Seq Scan on t (cost=0.00..195.00 rows=1 width=8) (actual rows=100.00 loops=1) Filter: ((a = 1) AND (b = 1)) Rows Removed by Filter: 9900
規劃器分別估算每個條件的 selectivity,得出與上面相同的 1% 估算值。然後,它假定這些條件是獨立的,因此將它們的 selectivity 相乘,得出最終的 selectivity 估算值僅為 0.01%。這是一個顯著的低估,因為實際匹配這些條件的行數(100 行)比估算值高出兩個數量級。
透過建立一個統計物件,指示 ANALYZE
在這兩個列上計算函式依賴多變數統計資訊,可以解決此問題。
CREATE STATISTICS stts (dependencies) ON a, b FROM t; ANALYZE t; EXPLAIN (ANALYZE, TIMING OFF, BUFFERS OFF) SELECT * FROM t WHERE a = 1 AND b = 1; QUERY PLAN ------------------------------------------------------------------------------- Seq Scan on t (cost=0.00..195.00 rows=100 width=8) (actual rows=100.00 loops=1) Filter: ((a = 1) AND (b = 1)) Rows Removed by Filter: 9900
在估算多個列集合的基數時,也會出現類似的問題,例如 GROUP BY
子句會生成的組的數量。當 GROUP BY
只列出一個列時,n-distinct 估算值(在 HashAggregate 節點返回的估算行數中可見)非常準確。
EXPLAIN (ANALYZE, TIMING OFF, BUFFERS OFF) SELECT COUNT(*) FROM t GROUP BY a; QUERY PLAN ----------------------------------------------------------------------------------------- HashAggregate (cost=195.00..196.00 rows=100 width=12) (actual rows=100.00 loops=1) Group Key: a -> Seq Scan on t (cost=0.00..145.00 rows=10000 width=4) (actual rows=10000.00 loops=1)
但是,如果沒有多變數統計資訊,在具有兩個列的 GROUP BY
查詢中,組數的估算值(如下例所示)會相差一個數量級:
EXPLAIN (ANALYZE, TIMING OFF, BUFFERS OFF) SELECT COUNT(*) FROM t GROUP BY a, b; QUERY PLAN -------------------------------------------------------------------------------------------- HashAggregate (cost=220.00..230.00 rows=1000 width=16) (actual rows=100.00 loops=1) Group Key: a, b -> Seq Scan on t (cost=0.00..145.00 rows=10000 width=8) (actual rows=10000.00 loops=1)
透過重新定義統計物件以包含這兩個列的 n-distinct 計數,估算值得到了很大的改善。
DROP STATISTICS stts; CREATE STATISTICS stts (dependencies, ndistinct) ON a, b FROM t; ANALYZE t; EXPLAIN (ANALYZE, TIMING OFF, BUFFERS OFF) SELECT COUNT(*) FROM t GROUP BY a, b; QUERY PLAN -------------------------------------------------------------------------------------------- HashAggregate (cost=220.00..221.00 rows=100 width=16) (actual rows=100.00 loops=1) Group Key: a, b -> Seq Scan on t (cost=0.00..145.00 rows=10000 width=8) (actual rows=10000.00 loops=1)
如 第 69.2.1 節中所述,函式依賴是一種非常便宜且高效的統計資訊型別,但其主要限制是全域性性(僅跟蹤列級別的依賴關係,而不跟蹤單個列值之間的依賴關係)。
本節將介紹多變數變體的MCV(most-common values) 列表,這是對 第 69.1 節中描述的每列統計資訊的直接擴充套件。這些統計資訊透過儲存單個值來解決上述限制,但自然會更昂貴,無論是在 ANALYZE
中構建統計資訊、儲存還是規劃時間方面。
讓我們再次檢視 第 69.2.1 節中的查詢,但這次是在同一組列上建立了MCV列表(確保刪除函式依賴,以確保規劃器使用新建立的統計資訊)。
DROP STATISTICS stts; CREATE STATISTICS stts2 (mcv) ON a, b FROM t; ANALYZE t; EXPLAIN (ANALYZE, TIMING OFF, BUFFERS OFF) SELECT * FROM t WHERE a = 1 AND b = 1; QUERY PLAN ------------------------------------------------------------------------------- Seq Scan on t (cost=0.00..195.00 rows=100 width=8) (actual rows=100.00 loops=1) Filter: ((a = 1) AND (b = 1)) Rows Removed by Filter: 9900
估算結果與使用函式依賴時一樣準確,這在很大程度上要歸功於該表相對較小且具有簡單的分佈,以及較少的不同值。在檢視第二個查詢之前(該查詢未得到函式依賴的特別好的處理),讓我們先檢查一下MCV列表。
使用返回集函式 pg_mcv_list_items
可以檢查MCV列表。
SELECT m.* FROM pg_statistic_ext join pg_statistic_ext_data on (oid = stxoid), pg_mcv_list_items(stxdmcv) m WHERE stxname = 'stts2'; index | values | nulls | frequency | base_frequency -------+----------+-------+-----------+---------------- 0 | {0, 0} | {f,f} | 0.01 | 0.0001 1 | {1, 1} | {f,f} | 0.01 | 0.0001 ... 49 | {49, 49} | {f,f} | 0.01 | 0.0001 50 | {50, 50} | {f,f} | 0.01 | 0.0001 ... 97 | {97, 97} | {f,f} | 0.01 | 0.0001 98 | {98, 98} | {f,f} | 0.01 | 0.0001 99 | {99, 99} | {f,f} | 0.01 | 0.0001 (100 rows)
這證實了兩個列中有 100 種不同的組合,並且所有組合的可能性都大致相同(每種 1% 的頻率)。基本頻率是從每列統計資訊計算出的頻率,就好像沒有多列統計資訊一樣。如果其中任何一列包含 NULL 值,則會在 nulls
列中識別出來。
在估算 selectivity 時,規劃器將所有條件應用於MCV列表中的項,然後將匹配項的頻率相加。有關詳細資訊,請參閱 src/backend/statistics/mcv.c
中的 mcv_clauselist_selectivity
。
與函式依賴相比,MCV列表有兩個主要優點。首先,列表儲存實際值,使得可以確定哪些組合是相容的。
EXPLAIN (ANALYZE, TIMING OFF, BUFFERS OFF) SELECT * FROM t WHERE a = 1 AND b = 10; QUERY PLAN --------------------------------------------------------------------------- Seq Scan on t (cost=0.00..195.00 rows=1 width=8) (actual rows=0.00 loops=1) Filter: ((a = 1) AND (b = 10)) Rows Removed by Filter: 10000
其次,MCV列表處理更廣泛的子句型別,而不僅僅是像函式依賴那樣的相等子句。例如,考慮同一表的以下範圍查詢:
EXPLAIN (ANALYZE, TIMING OFF, BUFFERS OFF) SELECT * FROM t WHERE a <= 49 AND b > 49; QUERY PLAN --------------------------------------------------------------------------- Seq Scan on t (cost=0.00..195.00 rows=1 width=8) (actual rows=0.00 loops=1) Filter: ((a <= 49) AND (b > 49)) Rows Removed by Filter: 10000
如果您在文件中發現任何不正確、與您在使用特定功能時的經驗不符或需要進一步說明的內容,請使用 此表單來報告文件問題。