支援的版本: 目前 (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

68.1. 列數估計範例 #

以下範例使用 PostgreSQL 迴歸測試資料庫中的資料表。另請注意,由於 ANALYZE 在產生統計資料時使用隨機取樣,因此在任何新的 ANALYZE 之後,結果會略有變化。

讓我們從一個非常簡單的查詢開始

EXPLAIN SELECT * FROM tenk1;

                         QUERY PLAN
-------------------------------------------------------------
 Seq Scan on tenk1  (cost=0.00..458.00 rows=10000 width=244)

查詢規劃器如何確定 tenk1 的基數已在第 14.2 節中涵蓋,但為了完整起見,在此重複說明。頁數和列數在 pg_class 中查找

SELECT relpages, reltuples FROM pg_class WHERE relname = 'tenk1';

 relpages | reltuples
----------+-----------
      358 |     10000

這些數字是最新的,自上次對資料表執行 VACUUMANALYZE 以來。然後,查詢規劃器會提取資料表中的實際目前頁數(這是一個廉價的操作,不需要資料表掃描)。如果這與 relpages 不同,則會相應地調整 reltuples,以得出目前的列數估計值。在上面的範例中,relpages 的值是最新的,因此列數估計值與 reltuples 相同。

讓我們繼續看一個 WHERE 子句中具有範圍條件的範例

EXPLAIN SELECT * FROM tenk1 WHERE unique1 < 1000;

                                   QUERY PLAN
-------------------------------------------------------------------​-------------
 Bitmap Heap Scan on tenk1  (cost=24.06..394.64 rows=1007 width=244)
   Recheck Cond: (unique1 < 1000)
   ->  Bitmap Index Scan on tenk1_unique1  (cost=0.00..23.80 rows=1007 width=0)
         Index Cond: (unique1 < 1000)

查詢規劃器檢查 WHERE 子句條件,並在 pg_operator 中查找運算子 < 的選擇性函數。這保存在 oprrest 欄位中,在這種情況下的條目是 scalarltselscalarltsel 函數從 pg_statistic 檢索 unique1 的直方圖。對於手動查詢,在更簡單的 pg_stats 檢視中查找更方便

SELECT histogram_bounds FROM pg_stats
WHERE tablename='tenk1' AND attname='unique1';

                   histogram_bounds
------------------------------------------------------
 {0,993,1997,3050,4040,5036,5957,7057,8029,9016,9995}

接下來,計算 < 1000佔據的直方圖比例。這是選擇性。直方圖將範圍劃分為等頻率儲存桶,因此我們只需找到我們的值所在的儲存桶,並計算 部分 儲存桶和 所有 之前的儲存桶。值 1000 顯然位於第二個儲存桶 (993–1997) 中。假設每個儲存桶內的值呈線性分布,我們可以將選擇性計算為

selectivity = (1 + (1000 - bucket[2].min)/(bucket[2].max - bucket[2].min))/num_buckets
            = (1 + (1000 - 993)/(1997 - 993))/10
            = 0.100697

也就是說,一個完整的儲存桶加上第二個儲存桶的線性分數,除以儲存桶的數量。現在可以將估計的列數計算為選擇性與 tenk1 基數的乘積

rows = rel_cardinality * selectivity
     = 10000 * 0.100697
     = 1007  (rounding off)

接下來,讓我們考慮一個 WHERE 子句中具有相等條件的範例

EXPLAIN SELECT * FROM tenk1 WHERE stringu1 = 'CRAAAA';

                        QUERY PLAN
----------------------------------------------------------
 Seq Scan on tenk1  (cost=0.00..483.00 rows=30 width=244)
   Filter: (stringu1 = 'CRAAAA'::name)

查詢規劃器再次檢查 WHERE 子句條件,並查找 = 的選擇性函數,即 eqsel。對於相等估計,直方圖沒有用;而是使用最常見的值MCVs)清單來確定選擇性。讓我們看看 MCV,以及稍後會用到的其他欄位

SELECT null_frac, n_distinct, most_common_vals, most_common_freqs FROM pg_stats
WHERE tablename='tenk1' AND attname='stringu1';

null_frac         | 0
n_distinct        | 676
most_common_vals  | {EJAAAA,BBAAAA,CRAAAA,FCAAAA,FEAAAA,GSAAAA,​JOAAAA,MCAAAA,NAAAAA,WGAAAA}
most_common_freqs | {0.00333333,0.003,0.003,0.003,0.003,0.003,​0.003,0.003,0.003,0.003}

由於 CRAAAA 出現在 MCV 清單中,因此選擇性只是最常見頻率(MCFs)清單中的相應條目

selectivity = mcf[3]
            = 0.003

與之前一樣,估計的列數只是這個值與 tenk1 基數的乘積

rows = 10000 * 0.003
     = 30

現在考慮相同的查詢,但使用不在MCV清單

EXPLAIN SELECT * FROM tenk1 WHERE stringu1 = 'xxx';

                        QUERY PLAN
----------------------------------------------------------
 Seq Scan on tenk1  (cost=0.00..483.00 rows=15 width=244)
   Filter: (stringu1 = 'xxx'::name)

中的常數MCV這是一個非常不同的問題:當值不在MCV清單

selectivity = (1 - sum(mcv_freqs))/(num_distinct - num_mcv)
            = (1 - (0.00333333 + 0.003 + 0.003 + 0.003 + 0.003 + 0.003 +
                    0.003 + 0.003 + 0.003 + 0.003))/(676 - 10)
            = 0.0014559

中時,如何估計選擇性。方法是利用該值不在清單中的事實,結合所有MCVMCV

rows = 10000 * 0.0014559
     = 15  (rounding off)

的頻率知識。也就是說,將所有 MCV 的頻率加總,並從 1 中減去,然後除以其他 不同值的數量。這相當於假設不是任何 MCV 的欄位部分在所有其他不同值之間平均分佈。請注意,沒有空值,因此我們不必擔心這些(否則我們也會從分子中減去空值分數)。然後,像往常一樣計算估計的列數

EXPLAIN SELECT * FROM tenk1 WHERE stringu1 < 'IAAAAA';

                         QUERY PLAN
------------------------------------------------------------
 Seq Scan on tenk1  (cost=0.00..483.00 rows=3077 width=244)
   Filter: (stringu1 < 'IAAAAA'::name)

先前帶有 unique1 < 1000 的範例是對 scalarltsel 實際作用的過度簡化;現在我們已經看到了使用 MCV 的範例,我們可以填寫更多詳細資訊。該範例就其所及是正確的,因為由於 unique1 是一個唯一欄位,因此它沒有 MCV(顯然,沒有值比任何其他值更常見)。對於非唯一欄位,通常會有一個直方圖和一個 MCV 清單,並且直方圖不包括由 MCV 代表的欄位母體部分。我們這樣做是因為它允許更精確的估計。在這種情況下,scalarltsel 直接將條件(例如,< 1000)套用到 MCV 清單的每個值,並加總條件為 true 的 MCV 的頻率。這給出了表格 MCV 部分內選擇性的精確估計。然後,以與上述相同的方式使用直方圖來估計表格非 MCV 部分中的選擇性,然後將兩個數字合併以估計整體選擇性。例如,考慮

SELECT histogram_bounds FROM pg_stats
WHERE tablename='tenk1' AND attname='stringu1';

                                histogram_bounds
-------------------------------------------------------------------​-------------
 {AAAAAA,CQAAAA,FRAAAA,IBAAAA,KRAAAA,NFAAAA,PSAAAA,SGAAAA,VAAAAA,​XLAAAA,ZZAAAA}

檢查 MCV 列表,我們發現前六個條目滿足條件 stringu1 < 'IAAAAA',而後四個條目不滿足,因此族群中 MCV 部分的選擇性是

selectivity = sum(relevant mvfs)
            = 0.00333333 + 0.003 + 0.003 + 0.003 + 0.003 + 0.003
            = 0.01833333

將所有 MCF 加總也告訴我們,MCV 代表的族群總比例為 0.03033333,因此直方圖代表的比例為 0.96966667(同樣,這裡沒有 null 值,否則我們必須將它們排除在外)。我們可以看見值 IAAAAA 幾乎落在第三個直方圖 bucket 的末端。規劃器使用一些相當粗略的關於不同字元頻率的假設,得出估計值 0.298387,表示小於 IAAAAA 的直方圖族群部分。然後我們結合 MCV 和非 MCV 族群的估計值

selectivity = mcv_selectivity + histogram_selectivity * histogram_fraction
            = 0.01833333 + 0.298387 * 0.96966667
            = 0.307669

rows        = 10000 * 0.307669
            = 3077  (rounding off)

在這個特定範例中,MCV 列表的校正相當小,因為欄位分佈實際上相當平坦(顯示這些特定值比其他值更常見的統計數據主要是由於抽樣誤差)。在更典型的情況下,某些值顯著比其他值更常見,這種複雜的過程可以有效地提高準確性,因為最常見值的選擇性會被精確地找到。

現在讓我們考慮一個在 WHERE 子句中有多個條件的情況

EXPLAIN SELECT * FROM tenk1 WHERE unique1 < 1000 AND stringu1 = 'xxx';

                                   QUERY PLAN
-------------------------------------------------------------------​-------------
 Bitmap Heap Scan on tenk1  (cost=23.80..396.91 rows=1 width=244)
   Recheck Cond: (unique1 < 1000)
   Filter: (stringu1 = 'xxx'::name)
   ->  Bitmap Index Scan on tenk1_unique1  (cost=0.00..23.80 rows=1007 width=0)
         Index Cond: (unique1 < 1000)

規劃器假設這兩個條件是獨立的,因此可以將子句的個別選擇性相乘

selectivity = selectivity(unique1 < 1000) * selectivity(stringu1 = 'xxx')
            = 0.100697 * 0.0014559
            = 0.0001466

rows        = 10000 * 0.0001466
            = 1  (rounding off)

請注意,從 bitmap index scan 估計返回的行數僅反映與索引一起使用的條件;這很重要,因為它影響後續 heap fetches 的成本估計。

最後,我們將檢查一個涉及 join 的查詢

EXPLAIN SELECT * FROM tenk1 t1, tenk2 t2
WHERE t1.unique1 < 50 AND t1.unique2 = t2.unique2;

                                      QUERY PLAN
-------------------------------------------------------------------​-------------------
 Nested Loop  (cost=4.64..456.23 rows=50 width=488)
   ->  Bitmap Heap Scan on tenk1 t1  (cost=4.64..142.17 rows=50 width=244)
         Recheck Cond: (unique1 < 50)
         ->  Bitmap Index Scan on tenk1_unique1  (cost=0.00..4.63 rows=50 width=0)
               Index Cond: (unique1 < 50)
   ->  Index Scan using tenk2_unique2 on tenk2 t2  (cost=0.00..6.27 rows=1 width=244)
         Index Cond: (unique2 = t1.unique2)

在 nested-loop join 之前,會評估 tenk1 上的限制條件 unique1 < 50。 這與之前的範圍範例類似處理。 這次值 50 落在 unique1 直方圖的第一個 bucket 中

selectivity = (0 + (50 - bucket[1].min)/(bucket[1].max - bucket[1].min))/num_buckets
            = (0 + (50 - 0)/(993 - 0))/10
            = 0.005035

rows        = 10000 * 0.005035
            = 50  (rounding off)

join 的限制條件是 t2.unique2 = t1.unique2。 運算符只是我們熟悉的 =,但是選擇性函數是從 pg_operatoroprjoin 欄位取得的,並且是 eqjoinseleqjoinsel 查找 tenk2tenk1 的統計資訊

SELECT tablename, null_frac,n_distinct, most_common_vals FROM pg_stats
WHERE tablename IN ('tenk1', 'tenk2') AND attname='unique2';

tablename  | null_frac | n_distinct | most_common_vals
-----------+-----------+------------+------------------
 tenk1     |         0 |         -1 |
 tenk2     |         0 |         -1 |

在這種情況下沒有MCVunique2 的資訊,並且所有值似乎都是唯一的(n_distinct = -1),因此我們使用一個依賴於兩個關聯的行數估計值(num_rows,未顯示,但 "tenk")以及欄位 null 比例(兩者皆為零)的演算法

selectivity = (1 - null_frac1) * (1 - null_frac2) / max(num_rows1, num_rows2)
            = (1 - 0) * (1 - 0) / max(10000, 10000)
            = 0.0001

這是,從每個關聯的 1 中減去 null 比例,然後除以較大關聯的行數(這個值在非唯一情況下會被縮放)。join 可能發出的行數計算為兩個輸入的笛卡爾積的基數,乘以選擇性

rows = (outer_cardinality * inner_cardinality) * selectivity
     = (50 * 10000) * 0.0001
     = 50

如果這兩個欄位有 MCV 列表,eqjoinsel 將使用 MCV 列表的直接比較來確定欄位族群中由 MCV 代表部分的 join 選擇性。 族群其餘部分的估計遵循此處顯示的相同方法。

請注意,我們將 inner_cardinality 顯示為 10000,也就是 tenk2 的未修改大小。 從 EXPLAIN 輸出中看起來,join 行的估計來自 50 * 1,也就是外部行的數量乘以每次在 tenk2 上進行內部索引掃描獲得的估計行數。 但事實並非如此:join 關係大小是在考慮任何特定 join 計畫之前估計的。 如果一切運作良好,則兩種估計 join 大小的方法將產生大致相同的答案,但由於捨入誤差和其他因素,它們有時會顯著不同。

對於那些對更多細節感興趣的人,表的大小(在任何 WHERE 子句之前)的估計是在 src/backend/optimizer/util/plancat.c 中完成的。 子句選擇性的通用邏輯位於 src/backend/optimizer/path/clausesel.c 中。 特定於運算符的選擇性函數主要位於 src/backend/utils/adt/selfuncs.c 中。

提交更正

如果您在文件中發現任何不正確、與您使用特定功能的經驗不符或需要進一步澄清之處,請使用此表單回報文件問題。