10 個你不知道的 PostgreSQL 功能:建立統計資訊

發表於2018-07-26

如果你曾使用 Postgres 做過一些效能優化,你或許已經使用過 EXPLAIN 。EXPLAIN 向你展示了 PostgreSQL planner 為提供的語句生成的執行計劃。它說明了語句涉及到的表將會使用順序掃描、索引掃描等方式進行掃描,在使用多表的情況下將會使用連線演算法。但是, Postgres 是如何產生這些規劃的?

決定使用哪種規劃的一個非常重要的輸入是 planner 收集到的資料統計。這些統計的資料能夠使 planner 評估執行規劃的某一部分會返回多少行,繼而影響到使用哪一種規劃或連線演算法。它們主要是通過執行 ANALYZE 或 VACUUM(和一些 DDL 命令,比如說 CREATE INDEX )來採集或更新的。

這些統計資訊由 planner 儲存在 pg_class 和 pg_statistics 中。Pg_class 基本上儲存了每個表和索引中的條目總數,以及它們所佔用的磁碟塊數。Pg_statistic 儲存關於每列的統計資訊,例如哪些列的 % 值為 nul l,哪些是最常見的值,直方圖邊界等。你可以檢視下面的示例,以瞭解 Postgres 在下表中為 col1 收集的統計資訊型別。下面的查詢輸出展示了 planner(正確地)預估表中列 col1 中有 1000 個不同的值,並且還對最常見的值、頻率等進行了其他預估。

請注意,我們已經查詢了 pg_stats(一個擁有更多可讀版本的列統計資訊的檢視)。

單列統計資料不足時

這些單列統計資訊可幫助 planner 估算你的條件選擇性(這是 planner 用來估算索引掃描將選擇多少行的內容)。 當查詢中存在多個條件時,planner 假定列(或 where 子句條件)彼此獨立。 當列相互關聯或相互依賴並導致 planner 低估或高估這些條件將返回的行數時,就不適用。

我們來看下面的幾個例子。 為了使查詢計劃易於閱讀,我們通過設定 max_parallel_workers_per_gather  為 0 來關閉每個查詢的並行性:

正如你看到的那樣,planner 估計 col1 的值為 1 的行數是 9584 ,而查詢返回的實際行數是 10000 ,所以相當準確。

當你在 column 1 和 column 2 都包含過濾器時會發生什麼情況。

planner 的估計減少了100倍! 讓我們試著理解為什麼發生這種情況。
第一個列的選擇性約為 0.001(1/1000),第二個列的選擇性為 0.01(1/100)。 要計算將由這兩個“獨立”條件過濾的行數,planner 會將它們的選擇性相乘。 所以,我們得到:

選擇性= 0.001 * 0.01 = 0.00001。

當它乘以我們在表中的行數即 10000000 時,我們得到 100。這就是 planner 對 100 的估計值的來源。 但是,這些列不是獨立的,那麼我們如何告知 planner ?

在 PostgreSQL 中建立統計資訊

在 Postgres 10 之前,沒有一種簡易的方式去告訴 planner 採集捕捉列之間關係的資料統計。但是, Postgres 10 有一個新特性正好解決了這個問題,可以使用 CREATE STATISTICS 來建立擴充套件統計的物件,告訴伺服器去採集這些有意思的相關列的額外的統計資訊。

函式依賴統計

回到我們先前評估的問題,col2 的值僅僅是 col1/10 。在資料庫的術語中,我們會說 col2 是函式依賴於 col1 ,也就是說,col1 的值足以決定 col2 的值,並且不存在有兩行資料擁有相同的 col1 值的同時有不同的 col2 值。因此,在 col2 列上的第二個過濾篩選並沒有移除任何行!但是,planner 捕捉到了足夠的統計資訊去知道這件事情。

讓我們來建立一個統計物件去捕獲這些列和執行分析(ANALYZE)所依賴的函式統計。

 

讓我們來看看現在的計劃是怎麼來的。

 

很好!讓我們看一下對計劃的測量。

 

看這裡,我們可以看到, Postgres 意識到 col1 完全決定 col2 ,因此用係數1來捕獲這些資訊。現在,所有的查詢都過濾這些列之後,計劃將會得到更好的評估。

ndistinct 統計

函式依賴是你可以在列之間捕獲的一種關係。 你可以捕獲的另一種統計資訊是一組列的不同值。 我們之前指出,planner 可以獲取每列不同值的統計數字,但再次合併多列時,這些統計資料往往是錯誤的。

這些不好的資料是在什麼時候影響我們的呢? 下面來看一個例子。

聚合行時,Postgres 選擇做雜湊聚合或組合。 如果它認為雜湊表合適,則選擇雜湊聚合,否則它會選擇對所有行進行排序,然後按照 col1、col2 對它們進行分組。

現在,planner 估計組的數量(等於 col1、col2 的不同值的數量)將為 100000。它預計到它沒有足夠的 work_mem 將該雜湊表儲存在記憶體中。 因此,它使用基於磁碟的排序來執行該查詢。 但是,正如在查詢計劃中所看到的那樣,實際行數僅為 1001。也許,我們有足夠的記憶體來執行雜湊聚合。

讓 planner 去捕獲 n_distinct 統計資訊,重新執行查詢並找出結果。

可以看到,現在的估算精度更高了(即 1000 ),查詢速度也提高了2倍左右。 通過執行下面的查詢,我們可以看到 planner 學到了什麼。

現實影響

在實際的生產模式中,你總是會有某些與資料庫不知道的相互依賴或關係的列。 以下是我們與 Citus 客戶見過的一些例子:

  • 有月份,季度和年份的列,因為你希望在報告中顯示按所有人分組的統計資訊。
  • 地理層次之間的關係。 例如。 具有國家,州和城市的列,並由它們來過濾/分組。

這裡的例子僅僅是在資料集中只有 10M 行的情況,並且我們已經看到,在存在相關列的情況下,使用 CREATE 統計資訊可顯著改善查詢計劃,並顯示效能改進。在 Citus 使用案例中,我們有客戶儲存數十億行資料,糟糕查詢計劃的影響可能非常嚴重。在上述示例中,當 planner 選擇了一個糟糕的查詢計劃時,我們不得不為 10M 行做一個基於磁碟的分類。想象一下如果是數十億行,那會有多糟糕。

 

 

相關文章