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

69.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。對於相等性估計,直方圖沒有用;而是使用最常見值MCVMCV)列表來確定選擇性。讓我們看一下 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 列表中,所以選擇性就是最常見頻率(MCF)列表中對應的條目

selectivity = mcf[3]
            = 0.003

和之前一樣,估計的行數就是這個選擇性與 tenk1 的基數的乘積

rows = 10000 * 0.003
     = 30

現在考慮相同的查詢,但常量不在 MCVMCV列表中

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)

這是一個完全不同的問題:當值不在 MCVMCV列表中時,如何估計選擇性。方法是利用該值不在列表中的事實,並結合所有 MCVMCVs

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的頻率加起來,用 1 減去這個和,然後除以其他不同值的數量。這相當於假設,不屬於任何 MCV 的那部分列值在所有其他不同值之間是均勻分佈的。請注意,這裡沒有空值,所以我們不必擔心它們(否則我們也要從分子中減去空值比例)。然後像往常一樣計算估計的行數

rows = 10000 * 0.0014559
     = 15  (rounding off)

前面 unique1 < 1000 的例子是對 scalarltsel 實際工作的過度簡化;現在我們已經看到了使用 MCV 的例子,可以補充一些細節。那個例子就其本身而言是正確的,因為 unique1 是一個唯一列,所以它沒有 MCV(顯然,沒有哪個值比其他任何值更常見)。對於一個非唯一列,通常既有直方圖,也有 MCV 列表,並且直方圖不包含由 MCV 代表的那部分列總體。我們這樣做是因為它能提供更精確的估計。在這種情況下,scalarltsel 會直接將條件(例如,“< 1000”)應用於 MCV 列表中的每個值,並累加那些滿足條件的 MCV 的頻率。這為表中屬於 MCV 的那部分提供了一個精確的選擇性估計。然後,直方圖會像上面那樣用於估計不屬於 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)

我們已經看到了 stringu1 的 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(同樣,沒有空值,否則我們也必須在這裡排除它們)。我們可以看到,值 IAAAAA 幾乎落在第三個直方圖桶的末尾。透過一些相當粗略的關於不同字元頻率的假設,規劃器得出的估計是,直方圖總體中小於 IAAAAA 的部分為 0.298387。然後我們結合 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)

請注意,估計從點陣圖索引掃描返回的行數僅反映了與索引一起使用的條件;這很重要,因為它會影響後續堆獲取的成本估計。

最後,我們將研究一個涉及連線的查詢

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)

tenk1 的限制條件 unique1 < 50 在巢狀迴圈連線之前進行評估。這與前面的範圍示例類似。這一次,值 50 落在了 unique1 直方圖的第一個桶中

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)

連線的限制條件是 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 |

在這種情況下,unique2 沒有 MCVMCV資訊,並且所有值似乎都是唯一的(n_distinct = -1),所以我們使用一個依賴於兩個關係行數估計值(num_rows,未顯示,但為“tenk”)以及列的空值比例(兩者都為零)的演算法

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

即,從每個關係的 1 中減去空值比例,然後除以較大關係的行數(在非唯一情況下,該值會被縮放)。連線可能產生的行數計算為兩個輸入的笛卡爾積的基數乘以選擇性

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

如果這兩列有 MCV 列表,eqjoinsel 將會使用直接比較 MCV 列表的方式來確定由 MCV 代表的那部分列總體中的連線選擇性。對於總體中其餘部分的估計則遵循這裡展示的相同方法。

注意,我們展示的 inner_cardinality 是 10000,也就是 tenk2 未經修改的大小。從 EXPLAIN 的輸出中看,連線行的估計值似乎來自於 50 * 1,即外部行的數量乘以每次對 tenk2 進行內部索引掃描所獲得的估計行數。但事實並非如此:連線關係的大小是在考慮任何特定連線計劃之前估算的。如果一切正常,那麼這兩種估算連線大小的方法會產生大致相同的答案,但由於舍入誤差和其他因素,它們有時會顯著不同。

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

提交更正

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