PostgreSQL中統計資訊計算

Rings發表於2016-10-14

#0. 前言背景.

在PostgreSQL中當我們查詢引擎在生成執行計劃時候,一個重要的問題是如何才能使得我們所生成的執行計劃是一個最優的結果。考慮到執行計劃最終需要與磁碟進行IO操作以讀取相應的資料,因此如果使得我們所生成的執行計劃其代價最優,這便是查詢引擎在查詢優化過程中需要考慮的一個問題。而執行代價又包括兩個方面:(1)計算資源;(2)IO資源。其中:計算資源主要涉及到CPU的計算時間,而IO資源主要包括磁碟的訪問資源等(這兩類資源具體包括哪些,在這裡就不在詳述了。)

對於一條查詢語句來說,其最終會落到磁碟的讀寫上面,因此衡量一個查詢引擎的好壞的標準是:是否可以將IO開銷做到最少。例如:對於查詢語句 SELECT * FROM tenk1 WHERE stringu1 = ‘xxx’; 其有無索引對於執行計劃有著不同的影響。

#1. 統計資訊

(1) 後設資料表 pg_statistic 和 pg_stats

對於pg_statistic裡面儲存了關於某個表的相關統計資訊,比如:該表的所有的Page資料和tuples資料量。 為了防止為授權使用者能夠修改其中的資料(pg_statistic只能由管理員來訪問,而pg_stat是檢視可由非管理員使用者檢視資料。)。當planner需要統計資料時候其會從該表中獲取,而並不是執行真正的表掃描,前提是對該表執行了analyze操作後。 relpages, reltuples, 當relpages發生變化後,在不執行analyze的情況會根據上一次relpage與reltuples的比例關係進行適當的變更。

對於selectivity 即:where子句中的選擇率(該值描述了在一張表裡面,對於某個值的區分度)。該選擇由 pg_operator中來進行查詢獲取相應的selectivity。其由pg_operator中的oprrest屬性所對應的函式來描述。 然後由該函式從pg_statistic中取出條件屬性相應的柱狀圖統計進行(historgram)。

例如:

對於語句:

其中1000在第二個桶裡面,[993 1997],假設在在每個桶裡的資料是線性分佈的。因此我們可以計算 < 1000時候的分佈概率。

因此相應的概率分佈為:
selectivity = (1 + (1000 – bucket[2].min)/(bucket[2].max – bucket[2].min))/num_buckets
= (1 + (1000 – 993)/(1997 – 993))/10
= 0.100697

相應的計算公式 選擇率的:
[Num(No. (X-1)) + (N- X.min)/(X.max-X.min)]/ NumOfBucket

然後相應的滿足條件的記錄數: rows = rel_cardinality * selectivity , 其中 rel_cardinality是該表中的記錄基數。即表中總共有多少記錄數。 對於等號,其柱狀圖的估計對於等號,其有效性不如其他操作。此時,我們使用最常用值來進行估計。在pg_stats中,我們根據表名和屬性名來查詢出 most_common_freqs, most_common_vals來。

例如:
“{1,100,10}”;”
對應的freqs
{0.97588,0.0126532,0.0106762}”

例如:對於SELECT * FROM tenk1 WHERE stringu1 = ‘xxx’; 查詢語句, 當我們常量 xxx 屬於 most common value時候,我們就可以使用該值對應的most common freqs作為該值的selectivity,但是當該值不在mcv列表裡面時候,此時的selectivity的估算就不能簡單的使用mcv列表裡面的值。 此時,需要一個新的計算方式來完成對非條件不在mcv列表中的情況。我們採用的是將現有的知識進行想融合的方式來進行選擇率的估計。

selectivity = (1 – sum(mvf))/(num_distinct – num_mcv)

該中方式下,將所有的most common freqs求和後, 然後計算其與1直接的差值,由此我們可以知道:在均勻分佈的情況下,如果條件為非mvc情況下,其most common freqs的值就為 (1- sum (msf))。對於該值的選擇率,我們此概率與整個最優的值的分佈之間的比例。

對於之前的 SELECT * FROM tenk1 WHERE unique1 < 1000; 過於簡單。當條件列為mcv列表中的時候可以使用上述的計算方式,當時當條件列不在mcv列表中的時候,對於非唯一的列,其可以是柱狀圖或者mcv列表,且在柱狀圖中其並未包括哪些在mcv列表中所表示的那些元組數量。當此種情況下,我們將選擇條件,應用於mcv列表中的所以值,並且將所有滿足該條件的所有的mcf值相加。

例如:對於查詢語句 SELECT * FROM tenk1 WHERE stringu1 < ‘IAAAAA’;

其相應的柱狀圖資訊如下(我們可以從pg_stats進行查詢獲得):

當對於條件中存在著連線操作時候的選擇率的估計與之前的做法相似。

例如對於查詢語句:

首先會計算條件 t1.unique1 < 50的選擇率,對於該條件的選擇率的計算參照上面討論。

在檔案src/backend/optimizer/util/plancat.c中計算一個表的大小(在任何的where子句之前),計運算元句的選擇率的由src/backend/optimizer/path/clausesel.c,關於操作符的選擇率的計算則由src/backend/utils/adt/selfuncs.c中的函式來完成。

當我們計算一個語句的選擇率時候,如果該語句存在著子句時候,我們就將這些子句的選擇率先計算出來,然後分別計算這些子句的選擇率,然後將這些子句的選擇相乘從而獲得整個語句的選擇率,前提條件是這些子句其有這相互獨立的概率條件,這與我們在概率論中的,兩個事件是相互獨立的事件時候,我們計算該這兩個事件的概率相似。但是在現實世界中,這些卻是不容易滿足的條件,因此我們需要根據合理的計算方式來計算一個語句的選擇率。

當然,對於統計資訊,由於其它是基於估算的基礎上,因此必然會存在著一個不可迴避的問題:該統計資訊的準確性(或者說不確定性),以及由此不準確所帶來的問題,導致查詢引擎在基於該統計資訊計算最優查詢訪問路徑時候的偏差(更加準確的是查詢計劃的“漂移”問題)。

相關文章