?
This document uses PHP Chinese website manual Release
下面的例子使用的PostgreSQL回歸測試數(shù)據(jù)庫中的表。輸出結(jié)果是從8.3版獲得的。 之前或之后版本的動作可能會有所變化。同時需要注意的是,在產(chǎn)生統(tǒng)計信息時,ANALYZE使用的是隨機采樣, 在使用一次新的ANALYZE之后,結(jié)果可能會發(fā)生輕微的改變。
讓我們以一個很簡單的查詢開始:
EXPLAIN SELECT * FROM tenk1; QUERY PLAN ------------------------------------------------------------- Seq Scan on tenk1 (cost=0.00..458.00 rows=10000 width=244)
規(guī)劃器如何判斷tenk1里面行的基數(shù)在Section 14.2里面介紹,為了完整, 在這里重復(fù)一下。行數(shù)或頁數(shù)是從pg_class里面查出來的:
SELECT relpages,reltuples FROM pg_class WHERE relname = 'tenk1'; relpages | reltuples ----------+----------- 358 | 10000
這些數(shù)字表示表中當(dāng)前最新的VACUUM或ANALYZE。 之后,規(guī)劃器取出表中當(dāng)前實際的塊號(這個操作的開銷很小,不需要掃描全表)。 如果與relpages不同,那么根據(jù)達到的一個當(dāng)前函數(shù)估計值,reltuples會進行一定的縮放。 在這種情況下,這個值是準確的,因此估計的行與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)
規(guī)劃器檢查WHERE子句條件,并為pg_operator中的
<執(zhí)行器查找可選函數(shù)。將保持在oprrest列中,
并且在這個例子中的條目是scalarltsel
。
scalarltsel
函數(shù)從pg_statistics為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"的部分找出來。這就是選擇性。直方圖把范圍分隔成相同頻率的段, 所以要做的只是把的數(shù)值所在的段找出來,然后計算它里面占的部分以及所有該值之前的部分。 值1000很明顯在第二個段(970-1943)里,因此,假設(shè)每個段里面的分布是線性的,那么就可以計算出選擇性:
selectivity = (1 + (1000 - bucket[2].min)/(bucket[2].max - bucket[2].min))/num_buckets = (1 + (1000 - 993)/(1997 - 993))/10 = 0.100697
也就是一個段加上第二個段的線性部分,除以總段數(shù)。那么估計的行數(shù)現(xiàn)在可以用選擇性和tenk1的基數(shù)之積計算:
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)
規(guī)劃器再次檢查WHERE子句條件,并為=(是eqsel
)查找可選函數(shù)。
對于等價估計而言,直方圖并不是有用的;相反,最常見的值(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,F(xiàn)CAAAA,F(xiàn)EAAAA,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}
因為MCV中有CRAAAA,那么可選項只是MCF列表中的一個相關(guān)條目:
selectivity = mcf[3] = 0.003
像之前一樣,行的估計數(shù)只是和前面一樣用tenk1的基數(shù)乘以選擇性:
rows = 10000 * 0.003 = 30
現(xiàn)在看看同樣的查詢,但是字符串常量是不在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)
這個時候的問題是完全不同的一個:在數(shù)據(jù)值不在MCV列表里面時, 如何估計選擇性就是完全另外一個問題了。解決方法是利用該值不在列表里頭的 事實,結(jié)合已知的所有MCV出現(xiàn)的頻率,用減法得出:
selectivity = (1 - sum(mvf))/(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
也就是,為MCV增加所有的頻率,并且從1減去,然后用其它無重復(fù)值的個數(shù)來分開。 這相當(dāng)于假設(shè)不是MCV中的列的分數(shù)巨暈的分布在所有其他不同值中。 需要注意的是,沒有NULL值,因此不需要擔(dān)心這些(否則需要從分子中減去NULL分數(shù))。 估算的行數(shù)然后照例計算:
rows = 10000 * 0.0014559 = 15 (rounding off)
之前帶有unique1 < 1000的例子是scalarltsel
實際執(zhí)行的簡單化。
現(xiàn)在已經(jīng)看過了使用MCV的例子,可以增加一些具體細節(jié)了。
這個例子這樣子是正確的,因為unique1是一個唯一屬性列,那么它沒有MCV(顯然,
沒有一個值能比其它值更通用)。對一個非唯一屬性列而言,通常會有直方圖和MCV列表,
并且直方圖不包括MCV表示的列總體那部分。在這種情況下,scalarltsel
直接應(yīng)用條件到每個
MCV列表的值上(如"< 1000"),并且增加那些條件判斷為真的MCV的頻率。這給出準確的是MCV表的部分的選擇的準確估計。
然后直方圖使用與上述方式相同的估計選擇表的部分,其不是MCV,那么組合這兩個數(shù)字來估計總的選擇性。例如,考慮
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)
我們已看到關(guān)于stringu1的MCV信息,這里是它的直方圖:
SELECT histogram_bounds FROM pg_stats WHERE tablename='tenk1' AND attname='stringu1'; histogram_bounds -------------------------------------------------------------------------------- {AAAAAA,CQAAAA,F(xiàn)RAAAA,IBAAAA,KRAAAA,NFAAAA,PSAAAA,SGAAAA,VAAAAA,XLAAAA,ZZAAAA}
檢查MCV列表,我們發(fā)現(xiàn)前6項滿足條件stringu1 < 'IAAAAA',而不是最后4項, 所以最常見的部分 MCV 選擇性是
selectivity = sum(relevant mvfs) = 0.00333333 + 0.003 + 0.003 + 0.003 + 0.003 + 0.003 = 0.01833333
累加所有的MCF,也告訴我們由 MCVs表示的常見的總比例是0.03033333,而且因此由直方圖表示的 比例是0.96966667。(再次,沒有NULL,否則這里我們排斥它們)我們可以看到IAAAAA值 落在第三段直方圖的結(jié)尾部分。關(guān)于不同字符串的頻率使用些較普通的假設(shè),規(guī)劃器達到估計0.298387為 直方圖中小于IAAAAA的部分。我們?nèi)缓蠼M合估計值為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列表的糾正很小,因為列分布實際上很平坦。 (統(tǒng)計分析顯示這些特殊值往往比其它的更常見大部分由于抽樣誤差) 在更典型的情況下這里有些值顯著的比其它的更常見,這復(fù)雜的處理過程,有用的提高了精度, 因為選擇性對于那些最常見的值來說,查找準確。
現(xiàn)在考慮一個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)
規(guī)劃器認為這兩個條件是獨立的,因此可以同時執(zhí)行語句的獨立查詢:
selectivity = selectivity(unique1 < 1000) * selectivity(stringu1 = 'xxx') = 0.100697 * 0.0014559 = 0.0001466 rows = 10000 * 0.0001466 = 1 (rounding off)
需要注意的是,從位圖索引掃描中返回的估計行數(shù)值影響索引使用的條件; 這一點很重要,因為它會影響之后的堆棧抓取估計開銷。
最后檢查一個包含連接的查找:
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: (t2.unique2 = t1.unique2)
在tenk1上的unique1 < 50限制在嵌套循環(huán)連接之前計算。這個條件是用類似上面的那個范圍例子的方法處理的。 但是這次數(shù)值50落在unique1的直方圖表的第一個段內(nèi):
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。操作符是我們熟悉的=,然而
可選函數(shù)是從pg_operator的oprjoin字段獲得的,并且是eqjoinsel
。
eqjoinsel
為tenk2和tenk1查找統(tǒng)計信息:
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的MCV信息,因為所有數(shù)值看上去都是唯一的,因此可以 使用一個只依賴唯一數(shù)值數(shù)目和NULL數(shù)目百分比的算法來給兩個表計算(選擇性):
selectivity = (1 - null_frac1) * (1 - null_frac2) * min(1/num_distinct1,1/num_distinct2) = (1 - 0) * (1 - 0) / max(10000,10000) = 0.0001
也就是說,把每個表都減去一里面NULL的比例,然后除以數(shù)值的最大值。連接可能選出來的行數(shù)是以嵌套循環(huán)里的兩個輸入值的笛卡爾積的總行數(shù),乘以選擇性計算出來的:
rows = (outer_cardinality * inner_cardinality) * selectivity = (50 * 10000) * 0.0001 = 50
這里有兩列的MCV列表, eqjoinsel
將直接使用MCV列表比較來決定連接
由MCV表示的常見列部分的選擇。下面常見的剩下的估計值跟顯示這里的方法相同。
需要注意的是,inner_cardinality表示為10000,也就是未修改的tenk2大小。 它可能出現(xiàn)從檢查EXPLAIN輸出,其連接行的估計值來自50 * 1,就是, 由外部行數(shù)乘以由每個內(nèi)部索引掃描的tenk2獲取的估計行數(shù)。但是這不是那種情況: 估計連接關(guān)系的大小在考慮任何特定的連接計劃之前。如果任何事情工作很好,那么兩種方式估計的連接大小將產(chǎn)生 相關(guān)的同樣的答案,但是由于四舍五入誤差和其它因素它們有時差異較明顯。
在src/backend/optimizer/util/plancat.c中有對一個表大小的估計(在任何WHERE字句之前)。 在src/backend/optimizer/path/clausesel.c中有對字句選擇性的通用邏輯。 在src/backend/utils/adt/selfuncs.c中有特定操作符的可選函數(shù)。