1. 程式人生 > 實用技巧 >BaikalDB技術實現內幕(三)--代價模型實現

BaikalDB技術實現內幕(三)--代價模型實現

此文轉載自:https://my.oschina.net/BaikalDB/blog/4715063
大咖揭祕Java人都栽在了哪?點選免費領取《大廠面試清單》,攻克面試難關~>>>

本系列文章主要介紹HTAP資料庫BaikalDB的技術實現細節

作者簡介:於正泉,百度商業平臺研發部高階研發工程師。主要從事分散式儲存、分散式資料庫等領域的工作,現主要負責BaikalDB SQL 效能優化,穩定性相關方向的研發工作。

歡迎關注 Star github.com/baidu/BaikalDB 國內加速映象庫gitee

BaikalDB系統簡介

BaikalDB是一個分散式可擴充套件的儲存系統,相容MySQL協議,整個系統的架構如下圖所示:

  • BaikalStore 負責資料儲存,資料分割槽按region組織,三個Store的三個region形成一個 Raft-group 實現三副本,多例項部署,Store例項宕機可以自動遷移 Region資料;
  • BaikalMeta 負責元資訊管理,包括分割槽,容量,許可權,均衡等, Raft 保障的3副本部署,Meta 宕機隻影響資料無法擴容遷移,不影響資料讀寫;
  • BaikalDB 負責前端SQL解析,查詢計劃生成執行,無狀態全同構多例項部署,宕機例項數不超過 qps 承載極限即可。

索引介紹

本文只介紹BaikalDB是怎樣基於代價進行索引選擇的。首先簡單回顧下BaikalDB的索引。 BaikalDB索引型別包括,主鍵索引,區域性索引,區域性唯一索引,全域性二級索引和倒排索引。主鍵索引是 BaikalDB 針對主鍵建立的索引,主鍵 key 值必須唯一。BaikalDB 使用主鍵值將整個表進行 range 劃分為多個 region 分割槽。 示例:

    CREATE TABLE `t` (
      `a` INT(10) NOT NULL,
      `b` DATETIME NOT NULL,
      `c` BIGINT(20) UNSIGNED NOT NULL,
      `d` TINYINT(3) NOT NULL,
      `e` VARCHAR(100)NO NULL,
       PRIMARY KEY (`a`),
       KEY b_c_key (`b`, `c`),  
       UNIQ KEY c_d_uniq (`c`, `d`)
    ) 

儲存模型如下圖:

其中倒排索引不適合做列資訊的統計,不在本文的討論範圍。全域性二級索引和主表資料儲存在不同分片上,統計資訊使用和區域性二級索引類似,只以區域性二級索引為例。

索引選擇

索引遵循最左字首匹配原則:只有相等的情況,才會使用下一級索引,如遇範圍匹配則不能使用下一級索引。示例:

  1. 索引(a,b),比如where b = 2;則不符合最左字首原則,無法使用該索引。只有在查詢條件中使用了建立索引時的第一個欄位,索引才會被使用。
  2. 索引(a,b,c,d),比如where a = 1 and b = 2 and c > 3 and d = 4;範圍查詢條件命中索引時無法繼續使用下一級,例子中c為範圍查詢,則d用不到索引,只能作為普通條件過濾。如果索引是(a,b,d,c),則可以用到全部索引列。

基於代價優選索引

對於能匹配到多個索引的SQL,需要選擇一個最優的代價最小的去執行。何為最優索引,檢索量是個很重要的指標,一條SQL獲取的資料量固定,檢索量越少說明該索引越高效,為了選出最優的索引,需要預估出使用某個索引的檢索量,那麼我們就需要一些統計資訊。 BaikalDB維護的統計資訊包括列直方圖Count-Min Sketchdistinct count等等。

  1. 列直方圖可以用來估計某一區間在整個範圍內的佔比,用於範圍查詢;
  2. Count-Min Sketch可以用來估計某一元素的出現頻率,用於等值查詢;
  3. distinct count可以用來評估該列的區分度。

查詢條件(範圍,等值,IN)在取樣資料中的佔比我們稱之為選擇率,再乘以表的總行數可以預估該條件需要檢索的行數。對於單列索引可以直接計算出索引的檢索行數,對於聯合索引涉及多列,假設每列是相互獨立的,那麼將每一列的選擇率相乘即可得到聯合索引的選擇率,進而可以預估聯合索引的檢索行數。統計資訊的生成和使用如下圖

接下來,我們將詳細介紹直方圖和Count-Min Sketch的實現和使用。

統計資訊

列直方圖

直方圖是一種對資料分佈情況的圖形表示,是一種二維統計圖表,它的兩個座標分別是統計樣本和該樣本對應的某個屬性的度量, 我們用直方圖描述列資料的分佈情況。 直方圖分為等寬直方圖和等深直方圖,在對統計資料進行分桶時,等寬直方圖比較難估計桶邊界,在資料傾斜較大時等深直方圖有較小的誤差,所以我們使用等深直方圖。直方圖橫座標表示列區間,縱座標表示落入該桶內的列取樣個數。如下圖,資料集合{1,1.5,2,3,4,6,7,8,9},生成三個桶[1,2],[3,6],[7,9],桶深均為3。

Count-Min Sketch

Count-Min Sketch 是一種可以處理等值查詢的資料結構。Count-Min Sketch維護了一個d*w的二維陣列,對於每個值,用d個獨立的hash函式對映到每一行的每一列中,並對應修改這d個位置的計數值。查詢一個值出現了多少次,依舊用這d個hash函式找到每一行中被對映到的位置,取這d個值的最小值作為估計值。如下圖:

統計資訊的構建

統計資訊總體構建流程

analyze 複用select查詢計劃,這樣只需要增加baikaldb的排序功能和baikalStore的抽樣功能

蓄水池抽樣

在構建直方圖的時候,無法將所有資料都讀出來進行排序,因此我們採用蓄水池抽樣演算法,用於生成均勻抽樣集合。蓄水池抽樣演算法用於從包含n個專案的集合S中選取k個樣本,其中n為一很大或未知的數量,尤其適用於不能把所有n個專案都存放到記憶體的情況。證明過程可以參考維基百科

列直方圖的構建

通過執行analyze語句,將抽樣請求下推到每個region上執行,然後將每一個region上的結果歸併排序。我們會根據表的總行數以及region的行數預估每個region的取樣行數,假設需要取樣S行,表的行數為T,region的行數為R,那麼預估這個region需要取樣 S * (T/R)行。 由於提前已經知道取樣總行數和直方圖桶個數,因此可以知道每個桶的深度。順序遍歷每個值V:如果V值等於上一個值,那麼將V放在與上一個值同一個桶裡,這樣保證每個值都只存在於同一個桶中。如果不等則判斷當前桶是否已滿,如果不是直接放入當前桶,否則開闢新桶。對於資料比較傾斜的場景,例如相同值個數大於平均桶深的某個閾值則單獨為該值開闢新桶。

Count-Min Sketch的構建

Count-Min Sketch相對比較簡單,每個region構建自己的Count-Min Sketch,然後再baikaldb聚合,二維陣列對應位置的值相加即可。

統計資訊的應用

索引代價計算

在查詢語句中,通常一條select語句會命中多個索引,上面講到我們會通過統計資訊來預估索引的檢索行數,接下來我們通過具體的例子來介紹使用統計資訊來計算索引的代價。 根據不同條件,一般分為等值查詢、多列查詢、範圍查詢。 以如下 schema 為例:

        CREATE TABLE table (
    	field0 bigint(20) NOT NULL DEFAULT '0' ,
    	field1 bigint(20) NOT NULL DEFAULT '0' ,
    	field2 bigint(20) NOT NULL DEFAULT '0' ,
    	field3 bigint(20) NOT NULL DEFAULT '0' ,
    	field4 bigint(20) NOT NULL DEFAULT '0' ,
    	field5 bigint(20) NOT NULL DEFAULT '0',
    	PRIMARY KEY (field0),
    	KEY field1_idx (field1),
    	KEY field2_idx (field2),
    	KEY field3_field4_idx (field3,field4)
    	)

例如:select * from table where field0 > a1 and field0 < a2 and field1 = b and field3 = c and field4 < d;

該條SQL可以命中_primary_key_、field1_idx、_field3_field4_idx_這三個索引。關於BaikalDB索引的使用可以參考之前的文章,本文不做過多贅述。

  • 等值查詢 對於索引field1_idx,可以使用Count-Min Sketch 獲取 field1等於a值的個數N,N除以取樣行數得出選擇率,再乘以錶行數預估檢索行數即為該索引的代價。

  • 範圍查詢 對於索引primay_key,可以使用直方圖估計區間(a1,a2)的佔比。具體演算法如下: 在前面介紹等深直方圖時,直方圖為三個桶,分別為[1,2],[3,6],[7,9],桶深均為3。如果我們想預估落在[1.2,8]這個區間內的行數,對應到直方圖上可以看到[3,6]這個區間是完全被覆蓋的,[1,2]和[7,9]各被覆蓋一部分。完全覆蓋很好理解,那麼覆蓋一部分怎麼計算呢?我們假設桶內資料是連續均勻分佈的,那麼可以通過比例去估算,(2-1.2) / (2 - 1) * 3 = 2,(8-7) / (9-7) = 1。

  • 多值查詢 對於索引_field3_field4_idx_,如何計算這個聯合索引的檢索行數,通常我們認為不同列之間是相互獨立的,因此把每列的選擇率相乘就能得到該聯合索引的選擇率。

使用主鍵和二級索引的區別,對於使用二級索引並且沒有索引覆蓋的查詢SQL,需要回表,回表的代價和順序掃描的代價是不同的,對於RocksDB來說回表的效能更差,所以在計算索引代價時需要回表的索引要乘以一個係數。

索引推薦

一些無索引/索引區分度低等原因導致檢索行數多、效能差的SQL,容易導致主機及DB資源消耗大,還可能影響同例項其他SQL,使DB吞吐能力降低,存在很大的風險。對於這種低效SQL,我們希望能給出建議索引,協助使用者優化效能。 首先對真實SQL模糊、抽象,生成SQL指紋,對SQL進行歸類,對每一類SQL進行統計,包括掃描行數、過濾行數,計算過濾率(過濾行數/掃描行數),篩選出過濾率加大的低效SQL。

抽象出來的SQL查詢條件沒有具體的值,所以使用distinct count資訊推薦索引,結合最左匹配規則,等值條件優先,其次是多值條件,最後是範圍查詢條件。為了提高索引的區分度,distinct count較大優先靠左排列。

後續工作

目前BaikalDB代價模型只實現了索引選擇,正在進一步完善中,主要工作如下:

  • 索引直方圖
    在聯合索引的代價計算是在假設不同列相互獨立的前提下計算的,如果聯合索引列之間相關性比較高,利用列直方圖計算出的索引代價誤差較高。我們將直接對聯合索引資料進行取樣生成索引直方圖,來降低索引列相關性的影響。
  • 統計資訊動態更新
    目前代價計算使用的統計資訊沒有實現動態更新,如果表的寫入或更新量較大可能造成統計資訊不準確。
  • Join Reorder
    在實際的業務場景中,多個表的 Join 語句是很常見的,而 Join 的執行效率和各個表參與 Join 的順序有關係。Join Reorder過程中需要根據表的資料量及資料分佈調整順序。