PostgreSQL 規(guī)劃器使用的統(tǒng)計信息

2021-08-27 15:43 更新
14.2.1. 單列統(tǒng)計信息
14.2.2. 擴展統(tǒng)計信息

14.2.1. 單列統(tǒng)計信息

如我們在上一節(jié)所見,查詢規(guī)劃器需要估計一個查詢要檢索的行數(shù),這樣才能對查詢計劃做出好的選擇。 本節(jié)對系統(tǒng)用于這些估計的統(tǒng)計信息進(jìn)行一個快速的介紹。

統(tǒng)計信息的一個部分就是每個表和索引中的項的總數(shù),以及每個表和索引占用的磁盤塊數(shù)。這些信息保存在pg_class表的reltuplesrelpages列中。 我們可以用類似下面的查詢查看這些信息:

SELECT relname, relkind, reltuples, relpages
FROM pg_class
WHERE relname LIKE 'tenk1%';

       relname        | relkind | reltuples | relpages
----------------------+---------+-----------+----------
 tenk1                | r       |     10000 |      358
 tenk1_hundred        | i       |     10000 |       30
 tenk1_thous_tenthous | i       |     10000 |       30
 tenk1_unique1        | i       |     10000 |       30
 tenk1_unique2        | i       |     10000 |       30
(5 rows)

這里我們可以看到tenk1包含 10000 行, 它的索引也有這么多行,但是索引遠(yuǎn)比表小得多(不奇怪)。

出于效率考慮,reltuplesrelpages不是實時更新的 ,因此它們通常包含有些過時的值。它們被VACUUM、ANALYZE和幾個 DDL 命令(例如CREATE INDEX)更新。一個不掃描全表的 VACUUMANALYZE操作(常見情況)將以它掃描的部分為基礎(chǔ)增量更新reltuples計數(shù),這就導(dǎo)致了一個近似值。在任何情況中,規(guī)劃器將縮放它在pg_class中找到的值來匹配當(dāng)前的物理表尺寸,這樣得到一個較緊的近似。

大多數(shù)查詢只是檢索表中行的一部分,因為它們有限制要被檢查的行的WHERE子句。 因此規(guī)劃器需要估算WHERE子句的選擇度,即符合WHERE子句中每個條件的行的比例。 用于這個任務(wù)的信息存儲在pg_statistic系統(tǒng)目錄中。 在pg_statistic中的項由ANALYZEVACUUM ANALYZE命令更新, 并且總是近似值(即使剛剛更新完)。

除了直接查看pg_statistic之外, 手工檢查統(tǒng)計信息的時候最好查看它的視圖pg_stats。pg_stats被設(shè)計為更容易閱讀。 而且,pg_stats是所有人都可以讀取的,而 pg_statistic只能由超級用戶讀?。ㄟ@樣可以避免非授權(quán)用戶從統(tǒng)計信息中獲取一些其他人的表的內(nèi)容的信息。pg_stats視圖被限制為只顯示當(dāng)前用戶可讀的表)。例如,我們可以:

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.363388 | I- 580                        Ramp+
         |           |            | I- 880                        Ramp+
         |           |            | Sp Railroad                       +
         |           |            | I- 580                            +
         |           |            | I- 680                        Ramp
 name    | t         |  -0.284859 | I- 880                        Ramp+
         |           |            | I- 580                        Ramp+
         |           |            | I- 680                        Ramp+
         |           |            | I- 580                            +
         |           |            | State Hwy 13                  Ramp
(2 rows)

注意,這兩行顯示的是相同的列,一個對應(yīng)開始于road表(inherited=t)的完全繼承層次, 另一個只包括road表本身(inherited=f)。

ANALYZEpg_statistic中存儲的信息量(特別是每個列的most_common_vals中的最大項數(shù)和histogram_bounds數(shù)組)可以用ALTER TABLE SET STATISTICS命令為每一列設(shè)置, 或者通過設(shè)置配置變量default_statistics_target進(jìn)行全局設(shè)置。 目前的默認(rèn)限制是 100 個項。提升該限制可能會讓規(guī)劃器做出更準(zhǔn)確的估計(特別是對那些有不規(guī)則數(shù)據(jù)分布的列), 其代價是在pg_statistic中消耗了更多空間,并且需要略微多一些的時間來計算估計數(shù)值。 相比之下,比較低的限制可能更適合那些數(shù)據(jù)分布比較簡單的列。

更多規(guī)劃器對統(tǒng)計信息的使用可參閱第 70 章

14.2.2. 擴展統(tǒng)計信息

常??梢钥吹接捎诓樵冏泳渲杏玫降亩鄠€列相互關(guān)聯(lián)而運行著糟糕的執(zhí)行計劃的慢查詢。規(guī)劃器通常會假設(shè)多個條件是彼此獨立的,這種假設(shè)在列值相互關(guān)聯(lián)的情況下是不成立的。由于常規(guī)的統(tǒng)計信息天然的針對個體列的性質(zhì),它們無法捕捉到跨列關(guān)聯(lián)的知識。不過,PostgreSQL有能力計算多元統(tǒng)計信息,它能捕捉這類信息。

由于可能的列組合數(shù)非常巨大,所以不可能自動計算多元統(tǒng)計信息??梢詣?chuàng)建擴展統(tǒng)計信息對象(更常被稱為統(tǒng)計信息對象)來指示服務(wù)器獲得跨感興趣列集合的統(tǒng)計信息。

統(tǒng)計信息對象可以使用CREATE STATISTICS命令創(chuàng)建。這樣一個對象的創(chuàng)建僅僅是創(chuàng)建了一個目錄項來表示對統(tǒng)計信息有興趣。實際的數(shù)據(jù)收集是由ANALYZE(或者是一個手工命令,或者是后臺的自動分析)執(zhí)行的。收集到的值可以在 pg_statistic_ext_data 目錄中看到。

ANALYZE基于它用來計算常規(guī)單列統(tǒng)計信息的表行樣本來計算擴展統(tǒng)計信息。由于樣本的尺寸會隨著表或者表列的統(tǒng)計信息目標(biāo)(如前一節(jié)所述)增大而增加,更大的統(tǒng)計信息目標(biāo)通常將會導(dǎo)致更準(zhǔn)確的擴展統(tǒng)計信息,同時也會導(dǎo)致更多花在計算擴展統(tǒng)計信息之上的時間。

下面的小節(jié)介紹當(dāng)前支持的擴展統(tǒng)計信息類型。

14.2.2.1. 函數(shù)依賴

最簡單的一類擴展統(tǒng)計信息跟蹤函數(shù)依賴,這是在數(shù)據(jù)庫范式定義中使用的概念。如果列a的值的知識足以決定列b的值,即不會有兩個行具有相同的a值但是有不同的b值,我們就說列b函數(shù)依賴于列 a。在一個完全規(guī)范化的數(shù)據(jù)庫中,函數(shù)依賴應(yīng)該僅存在于主鍵和超鍵上。不過,實際上很多數(shù)據(jù)集合會由于各種原因無法被完全規(guī)范化,常見的例子是為了性能而有意地反規(guī)范化。即使在一個完全規(guī)范化的數(shù)據(jù)庫中,也會有某些列之間的部分關(guān)聯(lián),這些可以表達(dá)成部分函數(shù)依賴。

函數(shù)依賴的存在直接影響了特定查詢中估計的準(zhǔn)確性。如果一個查詢包含獨立列和依賴列上的條件,依賴列上的條件不會進(jìn)一步降低結(jié)果的尺寸。但是如果沒有函數(shù)依賴的知識,查詢規(guī)劃器將假定條件是獨立的,導(dǎo)致對結(jié)果尺寸的低估。

要告知規(guī)劃器有關(guān)函數(shù)依賴的信息,ANALYZE可以收集跨列依賴的測度。評估所有列組之間的依賴程度可能會昂貴到不可實現(xiàn),因此數(shù)據(jù)收集被限制為針對那些在一個統(tǒng)計信息對象中一起出現(xiàn)的列組(用dependencies選項定義)。建議只對強相關(guān)的列組創(chuàng)建dependencies統(tǒng)計信息,以避免ANALYZE以及后期查詢規(guī)劃中不必要的開銷。

這里是一個收集函數(shù)依賴統(tǒng)計信息的例子:

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(城市),因此系數(shù)為1.0,而城市僅決定42%的郵編,意味著有很多城市(58%)有多個郵編。

在為涉及函數(shù)依賴列的查詢計算選擇度時,規(guī)劃器會使用依賴系數(shù)來調(diào)整針對條件的選擇度估計,這樣就不會產(chǎn)生低估。

14.2.2.1.1. 函數(shù)依賴的限制

當(dāng)前只有在考慮簡單等值條件(將列與常量值比較)和具有常量值的IN 子句時,函數(shù)依賴才適用。不會使用它們來改進(jìn)比較兩個列或者比較列和表達(dá)式的等值條件的估計, 也不會用它們來改進(jìn)范圍子句、LIKE或者任何其他類型的條件。

在用函數(shù)依賴估計時,規(guī)劃器假定在涉及的列上的條件是兼容的并且因此是冗余的。如果它們是不兼容的,正確的估計將是零行,但是那種可能性不會被考慮。例如,給定一個這樣的查詢

SELECT * FROM zipcodes WHERE city = 'San Francisco' AND zip = '94105';

規(guī)劃器將會忽視city子句,因為它不改變選擇度,這是正確的。不過,即便真地只有零行滿足下面的查詢,規(guī)劃器也會做出同樣的假設(shè)

SELECT * FROM zipcodes WHERE city = 'San Francisco' AND zip = '90210';

不過,函數(shù)依賴統(tǒng)計信息無法提供足夠的信息來排除這種情況。

在很多實際情況中,這種假設(shè)通常是能滿足的。例如,在應(yīng)用程序中可能有一個GUI僅允許選擇兼容的城市和郵編值用在查詢中。但是如果不是這樣,函數(shù)依賴可能就不是一個可行的選項。

14.2.2.2. 多元可區(qū)分值計數(shù)

單列統(tǒng)計信息存儲每一列中可區(qū)分值的數(shù)量。在組合多個列(例如GROUP BY a, b)時,如果規(guī)劃器只有單列統(tǒng)計數(shù)據(jù),則對可區(qū)分值數(shù)量的估計常常會錯誤,導(dǎo)致選擇不好的計劃。

為了改進(jìn)這種估計,ANALYZE可以為列組收集可區(qū)分值統(tǒng)計信息。和以前一樣,為每一種可能的列組合做這件事情是不切實際的,因此只會為一起出現(xiàn)在一個統(tǒng)計信息對象(用ndistinct選項定義)中的列組收集數(shù)據(jù)。將會為列組中列出的列的每一種可能的組合都收集數(shù)據(jù)。

繼續(xù)之前的例子,ZIP代碼表中的可區(qū)分值計數(shù)可能像這樣:

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個可區(qū)分值:ZIP代碼和州、ZIP代碼和城市、ZIP代碼+城市+周(事實上對于表中給定的一個唯一的ZIP代碼,它們本來就應(yīng)該是相等的)。另一方面,城市和州的組合只有27435個可區(qū)分值。

建議只對實際用于分組的列組合以及分組數(shù)錯誤估計導(dǎo)致了糟糕計劃的列組合創(chuàng)建ndistinct統(tǒng)計信息對象。否則,ANALYZE循環(huán)只會被浪費。

14.2.2.3. 多元MCV列表

為每列存儲的另一種統(tǒng)計信息是頻繁值列表。 這樣可以對單個列進(jìn)行非常準(zhǔn)確的估計,但是對于在多個列上具有條件的查詢,可能會導(dǎo)致嚴(yán)重的錯誤估計。

為了改善這種估計,ANALYZE可以收集列組合上的MCV列表。 與功能依賴和n-distinct系數(shù)類似,對每種可能的列分組進(jìn)行此操作都是不切實際的。 在這種情況下,甚至更是如此,因為MCV列表(與功能依賴性和n-distinct系數(shù)不同)存儲了公共列值。 因此,僅收集在使用mcv選項定義的統(tǒng)計對象中同時出現(xiàn)的那些列組的數(shù)據(jù)。

繼續(xù)前面的示例,郵政編碼表的MCV列表可能類似于以下內(nèi)容(與更簡單的統(tǒng)計信息不同,它需要一個函數(shù)來檢查MCV內(nèi)容):

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)

這表明城市和州的最常見組合是華盛頓特區(qū),實際頻率(在樣本中)約為0.35%。 組合的基本頻率(根據(jù)簡單的每列頻率計算)僅為0.0027%,導(dǎo)致兩個數(shù)量級的低估。

建議僅在實際在條件中一起使用的列的組合上創(chuàng)建MCV統(tǒng)計對象,對于這些組合,錯誤估計組數(shù)會導(dǎo)致糟糕的執(zhí)行計劃。 否則,只會浪費ANALYZE和規(guī)劃時間。


以上內(nèi)容是否對您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號