TiDB 優化器實現的基礎:統計資訊的收集

qiuyesuifeng發表於2016-11-04

> 收集統計資訊的意義 一個 SQL 資料庫裡,優化器實現的好壞對效能的影響是決定性的。一個未經優化的執行計劃和經過充分優化後的執行計劃,執行時間的差別往往是成千上萬倍。而對一個 SQL 優化器來說,統計資訊是必不可少的條件,只有依賴統計資訊提供的資料,優化器才可以正確估算不同的執行計劃的執行代價,以選擇最優的執行計劃。就像一個大廚無論多麼優秀,沒有上等食材也是無法做出美味的飯菜。

### 統計資訊包含的內容 統計資訊有兩類,包括 Table 統計資訊和 Column 統計資訊。 Table 統計資訊包含 Row Count 和 Row Size。 Column 統計資訊包含 Null Count,Max Value,Min Value,Distinct Count 以及 Histogram。其中 Histogram 用來記錄這個 Column 的資料詳細分佈,可以用來估算大於、小於或等於某個 Value 的 Row Count。 ### 統計資訊採集的步驟 1)在 TiDB 執行 ANALYZE TABLE 語句,手動觸發收集動作。 我們知道,一個 Table 的資料往往是在不斷變化的,我們無法保證統計資訊時刻保持在最新狀態,總會有一定的誤差,如果我們不及時更新,隨著時間的推進,這個誤差可能會越來越大,影響到優化器的優化效果。 有時我們會需要讓統計資訊更新的頻率低一些來降低系統的壓力,因為每次的統計資訊收集都是開銷很大的操作。有時我們會需要立即更新統計資料,因為我們剛剛向一個表匯入了大量的資料,馬上就需要查詢。 所以定期更新統計資訊的功能,我們希望可以用獨立的模組,用更靈活的策略來實現,TiDB 本身只需要支援基本的手動觸發就可以了。

2)全表掃描。 全表掃描的執行過程比較長,整個掃表的任務會被分解為一系列 Region 的小請求,每個 Region 的請求會返回該 Region 包含的 Table 的部分資料。

3)用掃描得到的資料,記錄 Row Count 和 Row Size,並對資料取樣。 掃描得到的資料量有可能會非常大,以至於無法把全部資料保留在記憶體裡進行處理,所以需要進行取樣,當取樣的概率均勻的時候,計算生成的統計資訊的誤差是可以接受的。這裡我們設定的取樣數是 1 萬,無論 Table 有多大,最後保留的樣本數不會超過 1 萬。後面會詳細介紹取樣時使用到的演算法。

4)取樣資料生成 Column 統計資訊,並儲存到 KV。 取樣得到的資料會進行計算生成 Histogram。

取樣演算法

要得到一個均勻分佈的取樣池,一個最簡單的演算法是,當我掃描整個表的時候,讀到的每一行,都以一個固定的概率決定是否加入取樣池,這個概率 P =(取樣池大小/表的行數)。但是由於在掃描之前,我們並不知道一個表總共有多少行,所以如果使用這個演算法進行取樣,就需要掃描兩次,第一次獲取整個表的行數,第二次進行真正的取樣。 一個更優化的演算法,蓄水池演算法,可以在表的大小未知的情況下,一次掃描得到均勻分佈的取樣池。

演算法的實現 假如我們的樣本池大小為 $M = 100$ ,從頭開始掃描全表,當我們讀到的記錄個數 $K < 100$ 時,我們把每一條記錄都加入取樣池,這樣保證了在記錄總數小於取樣池大小時,所有記錄都會被選中。 我們繼續掃描,當我們掃描到的第 $K = 101 條 $ 時,我們用概率 $P = (M/K) = (100/101)$ 決定是否把這個新的記錄加入取樣池,如果加入了取樣池,取樣池的總數會超過 $M$ 的限制,這時我們需要隨機選擇一箇舊的取樣丟掉,保證取樣池大小不會超過限制。 執行這樣取樣演算法一直到全表掃描結束,我們可以得到一個均勻分佈的取樣池。

演算法的證明 這個演算法可以用歸納法來證明,如果表的大小 $N <= K$,所有記錄都會放入取樣池,滿足均勻分佈的要求。 現在假設當讀取完 K 個元素時,取樣池滿足均勻分佈的要求,取樣概率 $P = (M / K)$,當讀取第 $K + 1$ 個記錄時,我們應用蓄水池演算法,以 $P' = M / (K + 1)$ 的概率決定是否加入取樣池,這時出現了兩種情況,被加入和沒有被加入,我們繼續分析這兩種情況下,這條記錄被選中的概率。 如果記錄沒有被選中,舊樣本被保留概率是 $Po1 =(1 - P') P = (1 - M / (K + 1)) (M / K)$, 新樣本被保留的概率是 Pn1 = 0。 如果記錄被選中,取樣池中已有的取樣有 (M - 1) / M 的概率被保留,舊樣本概率是 $Po2 = P' P Po = (M / (K + 1)) (M / K) ( (M -1) / M)$,新樣本的整體概率是 $Pn2 = P' = M / (K + 1)$。 把兩種情況下的概率相加, 得到舊取樣被保留的概率 $Po = Po1 + Po2 = (1 - M / (K + 1)) (M / K) + (M / (K + 1)) (M / K) *( (M -1) / M) = M / (K + 1)$,新取樣被保留的概率為 $Pn = Pn1 + Pn2 = 0 + M / (K + 1) = M / (K + 1)$,所有采樣被保留的概率都是 $M / (K + 1)$,滿足均勻分佈的要求。

Histogram Histogram 的型別主要有兩種,Frequency Histogram 和 Height-Balanced Histogram。 當 $NDV < Bucket Count$ 時,Frequency 可以包含全部 value 分佈資訊,每一個 distinct value 佔用一個 bucke。 但是當 $NDV > bucket count$ 時,Frequency Histogram 沒有足夠的 bucket 存放 value,我們就需要用另外的 Histogram 型別,比如 Height-Balanced Histogram。 Height-Balanced Histogram 把所有 value 從小到大排序,平均放到所有 bucket 裡,但是缺點是無法記錄有哪些 popular value。 Oracle 還實現了一種 Hibrid Histogram,綜合了 Frequency Histogram 和 Height-Balanced Histogram 的優點,TiDB 實現的 Histogram 主要參考的就是 Oracle 的 Hibraid Histogram。 Hibrid Histogram 包含 N 個 bucket,我們設定的 N 的預設值是 256,每個 bucket 包含三個欄位 numbervaluerepeatnumber 代表放在這個 bucket 裡的最後一個 value 在 table 裡排序後的 offset,value 就是放在這個 bucket 裡的最大的那一個 value,repeat 代表最大的 value 的個數。 Hibrid Histogram 在生成的過程中,如果一個 bucket 裝滿了,遇到下一個 value 的時候,比較一下這個新的 value 和前一個 value 是否相等,如果相等,增加 repeat 值,直到遇到一個更大的 value,換下一個 bucket 存放這個 value,這樣保證任何一個 value 只會在 一個 bucket 記憶體在,相比 Height-Balanced Histogram,可以包含更準確的 value 分佈資訊。 我們用一個例項來說明 Hibrid Histogram 是如何儲存 value 分佈資訊的。 給定 value 集合 $['a', 'a', 'b', 'c', c', 'c', 'c', 'd', 'd', ‘e’]$ 和 bucket count 3,生成的 histogram 如下:

>[number, value, repeat] [2, 'b', 0] [6, 'c', 3] [9, 'e', 0]

我們可以看到這個集合內, 'c' 的個數有 4 個,在第二個 bucket 裡準確記錄了 'c' 的 repeat 數量 3,這樣我們在查詢條件為 where column = 'c'的時候,就可以準確的估算執行開銷。 統計資訊的收集,使 TiDB 的優化器掌握資料分佈詳情,準確估算執行開銷,從而實現高效的 CBO (cost base optimization)。


這是對 PingCAP 第 15 期 NewSQL Meetup 中《TiDB 優化器統計資訊的採集》這一議題的乾貨整理。

作為一個前沿領域的技術公司,PingCAP 希望能為在國內營造真正關注技術本身的社群和 Meetup 貢獻一分力量。自2016年3月5日開始 ,我們在每週六舉辦 NewSQL Meetup,通過 1-2 個議題與大家交流討論,並跟大家分享 TiDB 最新的一些進展和我們的思考,希望藉由這個機會讓更多人關注到下一代分散式資料庫。同時,我們也會定期從 Meetup 分享中篩選議題以文章形式與大家做進一步深度探討。歡迎感興趣的小夥伴關注 TiDB 專案,盼望各路大牛加入 PingCAP。

更多原創文章乾貨分享,請關注公眾號
  • TiDB 優化器實現的基礎:統計資訊的收集
  • 加微信實戰群請加微信(註明:實戰群):gocnio

相關文章