正如我們在上一節看到的,查詢規劃器需要估算查詢返回的行數,以便做出最佳的查詢計劃選擇。本節將簡要介紹系統用於這些估算的統計資訊。
統計資訊的一個組成部分是每個表和索引的總條目數,以及每個表和索引佔用的磁碟塊數。這些資訊儲存在 pg_class
表的 reltuples
和 relpages
列中。我們可以透過類似這樣的查詢來檢視它
SELECT relname, relkind, reltuples, relpages FROM pg_class WHERE relname LIKE 'tenk1%'; relname | relkind | reltuples | relpages ----------------------+---------+-----------+---------- tenk1 | r | 10000 | 345 tenk1_hundred | i | 10000 | 11 tenk1_thous_tenthous | i | 10000 | 30 tenk1_unique1 | i | 10000 | 30 tenk1_unique2 | i | 10000 | 30 (5 rows)
在這裡我們可以看到 tenk1
包含 10000 行,它的索引也是如此,但這些索引(毫不奇怪)比表本身小得多。
出於效率原因,reltuples
和 relpages
不是即時更新的,因此它們通常包含略微過時的值。它們由 VACUUM
、ANALYZE
和一些 DDL 命令(如 CREATE INDEX
)更新。一個不掃描整個表的 VACUUM
或 ANALYZE
操作(這很常見)將基於它掃描的部分表來增量更新 reltuples
計數,從而得到一個近似值。無論如何,規劃器都會將它在 pg_class
中找到的值縮放到匹配當前的物理表大小,從而得到一個更接近的近似值。
大多數查詢只會檢索表中一小部分行,因為 WHERE
子句限制了要檢查的行。因此,規劃器需要估算 WHERE
子句的選擇性,即每一項條件匹配的行所佔的比例。用於此任務的資訊儲存在 pg_statistic
系統目錄中。 pg_statistic
中的條目由 ANALYZE
和 VACUUM ANALYZE
命令更新,並且即使在剛更新時也是近似值。
與直接檢視 pg_statistic
相比,在手動檢查統計資訊時,最好檢視其檢視 pg_stats
。 pg_stats
的設計更易於閱讀。此外,pg_stats
對所有人可讀,而 pg_statistic
僅對超級使用者可讀。(這可以防止沒有許可權的使用者從統計資訊中瞭解其他人的表內容。 pg_stats
檢視被限制為只顯示當前使用者可以讀取的表的行。)例如,我們可以這樣做
SELECT attname, inherited, n_distinct, array_to_string(most_common_vals, E'\n') as most_common_vals FROM pg_stats WHERE tablename = 'road'; attname | inherited | n_distinct | most_common_vals ---------+-----------+------------+------------------------------------ name | f | -0.5681108 | I- 580 Ramp+ | | | I- 880 Ramp+ | | | Sp Railroad + | | | I- 580 + | | | I- 680 Ramp+ | | | I- 80 Ramp+ | | | 14th St + | | | I- 880 + | | | Mac Arthur Blvd+ | | | Mission Blvd+ ... name | t | -0.5125 | I- 580 Ramp+ | | | I- 880 Ramp+ | | | I- 580 + | | | I- 680 Ramp+ | | | I- 80 Ramp+ | | | Sp Railroad + | | | I- 880 + | | | State Hwy 13 Ramp+ | | | I- 80 + | | | State Hwy 24 Ramp+ ... thepath | f | 0 | thepath | t | 0 | (4 rows)
請注意,對於同一列會顯示兩行,一行對應以 road
表開頭的完整繼承層級(inherited
=t
),另一行僅包含 road
表本身(inherited
=f
)。(為簡潔起見,我們僅顯示了 name
列的前十個最常見值。)
由 ANALYZE
儲存在 pg_statistic
中的資訊量,特別是每個列的 most_common_vals
和 histogram_bounds
陣列的最大條目數,可以透過 ALTER TABLE SET STATISTICS
命令在列級別上設定,或者透過設定 default_statistics_target 配置變數來全域性設定。預設限制目前是 100 個條目。提高此限制可能有助於提高規劃器估算的準確性,尤其對於資料分佈不規律的列,但代價是 pg_statistic
佔用更多空間,計算估算值也需要更多時間。反之,對於資料分佈簡單的列,較低的限制可能就足夠了。
有關規劃器如何使用統計資訊的更多詳細資訊,請參閱 第 69 章。
由於查詢子句中使用的多個列之間存在相關性,經常會導致查詢執行緩慢並生成糟糕的執行計劃。規劃器通常假設多個條件是相互獨立的,當列值相關時,這種假設就不成立了。常規統計資訊由於其單列的性質,無法捕捉到跨列相關性的任何資訊。然而,PostgreSQL 能夠計算多變數統計資訊,這可以捕捉到這種資訊。
由於可能的列組合數量非常龐大,自動計算多變數統計資訊是不切實際的。取而代之的是,可以建立擴充套件統計資訊物件(通常簡稱為統計資訊物件),以指示伺服器收集跨感興趣的列集進行統計。
統計資訊物件使用 CREATE STATISTICS
命令建立。建立此類物件只是建立了一個目錄條目,表示對該統計資訊的興趣。實際的資料收集由 ANALYZE
執行(無論是手動命令還是後臺自動分析)。收集到的值可以在 pg_statistic_ext_data
目錄中檢視。
ANALYZE
根據它為收集常規單列統計資訊所取樣的錶行樣本來計算擴充套件統計資訊。由於透過提高表或其任何列的統計資訊目標(如上一節所述)來增加取樣大小,因此較大的統計資訊目標通常會導致更準確的擴充套件統計資訊,以及計算它們所需的時間更長。
以下子節將描述目前支援的擴充套件統計資訊型別。
擴充套件統計資訊中最簡單的型別是跟蹤函式依賴,這是資料庫正規化定義中使用的概念。我們說列 b
函式依賴於列 a
,如果知道 a
的值足以確定 b
的值,也就是說,沒有兩行具有相同的 a
值但不同的 b
值。在完全正規化化的資料庫中,函式依賴應該只存在於主鍵和超鍵上。然而,在實踐中,許多資料集由於各種原因並未完全正規化化;出於效能原因的有意反正規化化是一個常見例子。即使在完全正規化化的資料庫中,某些列之間也可能存在部分相關性,這可以表示為部分函式依賴。
函式依賴的存在直接影響某些查詢估算的準確性。如果查詢同時包含獨立列和依賴列上的條件,則對依賴列的條件不會進一步減小結果集的大小;但如果沒有函式依賴的知識,查詢規劃器將假定這些條件是獨立的,從而低估結果集的大小。
為了告知規劃器關於函式依賴的資訊,ANALYZE
可以收集跨列依賴性的測量資料。評估所有列集之間的依賴程度成本過高,因此資料收集僅限於出現在具有 dependencies
選項定義的統計資訊物件中的列組。建議僅為強相關的列組建立 dependencies
統計資訊,以避免在 ANALYZE
和後續查詢規劃中產生不必要的開銷。
以下是收集函式依賴統計資訊的示例
CREATE STATISTICS stts (dependencies) ON city, zip FROM zipcodes; ANALYZE zipcodes; SELECT stxname, stxkeys, stxddependencies FROM pg_statistic_ext join pg_statistic_ext_data on (oid = stxoid) WHERE stxname = 'stts'; stxname | stxkeys | stxddependencies ---------+---------+------------------------------------------ stts | 1 5 | {"1 => 5": 1.000000, "5 => 1": 0.423130} (1 row)
在這裡可以看到,第 1 列(郵政編碼)完全確定第 5 列(城市),因此係數為 1.0;而城市僅在約 42% 的情況下確定郵政編碼,這意味著許多城市(58%)由不止一個郵政編碼表示。
在計算涉及函式依賴列的查詢的選擇性時,規劃器會使用依賴性係數調整每個條件的估計選擇性,以避免產生低估。
函式依賴目前僅在考慮將列與常量值進行比較的簡單相等條件以及帶有常量值的 IN
子句時應用。它們不用於改進比較兩個列或將列與表示式進行比較的相等條件,也不用於範圍子句、LIKE
或任何其他型別的條件。
在進行函式依賴估算時,規劃器假定涉及列上的條件是相容的,因此是冗餘的。如果它們不相容,正確的估算將是零行,但這種情況不會被考慮。例如,對於如下查詢:
SELECT * FROM zipcodes WHERE city = 'San Francisco' AND zip = '94105';
規劃器將忽略 city
子句,因為它不會改變選擇性,這是正確的。然而,它會對以下情況做出相同的假設:
SELECT * FROM zipcodes WHERE city = 'San Francisco' AND zip = '90210';
即使實際上沒有行滿足此查詢。然而,函式依賴統計資訊不足以得出此結論。
在許多實際情況下,這種假設通常是滿足的;例如,應用程式中可能有一個 GUI,只允許選擇相容的城市和郵政編碼值來用於查詢。但如果情況並非如此,函式依賴可能就不是一個可行的選項。
單列統計資訊儲存每列的獨立值數量。當組合多個列時(例如,對於 GROUP BY a, b
),對獨立值數量的估算經常出錯,當規劃器僅擁有單列統計資料時,這會導致其選擇糟糕的計劃。
為了改進此類估算,ANALYZE
可以為列組收集 n-distinct 統計資訊。與之前一樣,為每個可能的列組合執行此操作是不切實際的,因此資料僅為出現在具有 ndistinct
選項定義的統計資訊物件中的列組收集。將為列集中每個可能的兩個或多個列的組合收集資料。
繼續前面的示例,表中郵政編碼的 n-distinct 計數可能如下所示
CREATE STATISTICS stts2 (ndistinct) ON city, state, zip FROM zipcodes; ANALYZE zipcodes; SELECT stxkeys AS k, stxdndistinct AS nd FROM pg_statistic_ext join pg_statistic_ext_data on (oid = stxoid) WHERE stxname = 'stts2'; -[ RECORD 1 ]-------------------------------------------------------- k | 1 2 5 nd | {"1, 2": 33178, "1, 5": 33178, "2, 5": 27435, "1, 2, 5": 33178} (1 row)
這表明有三個列組合擁有 33178 個不同的值:郵政編碼和州;郵政編碼和城市;以及郵政編碼、城市和州(它們都相等的事實是因為郵政編碼本身在這個表中是唯一的)。另一方面,城市和州的組合只有 27435 個不同的值。
建議僅為實際用於分組且對分組數量的錯誤估算導致了糟糕的計劃的列組合建立 ndistinct
統計資訊物件。否則,ANALYZE
的週期就會被浪費。
為每列儲存的另一種統計資訊是最常見值列表。這允許對單個列進行非常準確的估算,但可能導致對具有多個列條件的查詢產生嚴重的錯誤估算。
為了改進此類估算,ANALYZE
可以為列組合收集 MCV 列表。與函式依賴和 n-distinct 係數類似,為每個可能的列組合執行此操作是不切實際的。在此情況下更是如此,因為 MCV 列表(與函式依賴和 n-distinct 係數不同)確實儲存了常見的列值。因此,資料僅為出現在具有 mcv
選項定義的統計資訊物件中的列組收集。
繼續前面的示例,郵政編碼表的 MCV 列表可能如下所示(與更簡單的統計資訊型別不同,需要一個函式來檢查 MCV 內容)
CREATE STATISTICS stts3 (mcv) ON city, state FROM zipcodes; ANALYZE zipcodes; SELECT m.* FROM pg_statistic_ext join pg_statistic_ext_data on (oid = stxoid), pg_mcv_list_items(stxdmcv) m WHERE stxname = 'stts3'; index | values | nulls | frequency | base_frequency -------+------------------------+-------+-----------+---------------- 0 | {Washington, DC} | {f,f} | 0.003467 | 2.7e-05 1 | {Apo, AE} | {f,f} | 0.003067 | 1.9e-05 2 | {Houston, TX} | {f,f} | 0.002167 | 0.000133 3 | {El Paso, TX} | {f,f} | 0.002 | 0.000113 4 | {New York, NY} | {f,f} | 0.001967 | 0.000114 5 | {Atlanta, GA} | {f,f} | 0.001633 | 3.3e-05 6 | {Sacramento, CA} | {f,f} | 0.001433 | 7.8e-05 7 | {Miami, FL} | {f,f} | 0.0014 | 6e-05 8 | {Dallas, TX} | {f,f} | 0.001367 | 8.8e-05 9 | {Chicago, IL} | {f,f} | 0.001333 | 5.1e-05 ... (99 rows)
這表明最常見的城市和州組合是華盛頓特區,實際頻率(在樣本中)約為 0.35%。組合的基礎頻率(從簡單的單列頻率計算得出)僅為 0.0027%,導致低估了兩個數量級。
建議建立MCV統計資訊物件僅用於實際一起出現在條件中的列組合,並且對分組數量的錯誤估算導致了糟糕的計劃。否則,ANALYZE
和規劃週期就會被浪費。
如果您在文件中發現任何不正確、與您的實際經驗不符或需要進一步澄清的內容,請使用 此表單 報告文件問題。