1. 程式人生 > >高效能Mysql 入門到放棄 之 B+-Tree (與B-Tree以及Binary Tree的對比解析)

高效能Mysql 入門到放棄 之 B+-Tree (與B-Tree以及Binary Tree的對比解析)

問題由來:

索引:大家平常說的還有用的索引,如果沒特別標明或者宣告都是B-Tree索引,大多數Mysql引擎都支援這種索引,而Msyql常用引擎InnoDB等常為B+-Tree。

提出問題:

!!!注意是B樹 B樹 不是讀作B減樹 B-Tree,怎麼讀心裡要有B數的。

WTF,B-Tree是什麼Tree,是BinaryTree麼?並不是那麼什麼是B+-Tree呢?有什麼區別?我們平常用的二叉搜尋樹的時間複雜度不是Log(n)麼,難道不夠優秀麼?

開始解決問題:

預備知識

磁碟IO

· 系統讀取磁碟是將磁碟的基本單位---磁碟塊(Block)讀取出來。位一同一磁碟塊的資料會被一次性讀取出來。磁碟讀取IO是機械動作,時間大概為記憶體讀取的十萬多倍。所以磁碟IO讀寫速度成為索引效能的主要指標。

1.複習一下二叉搜尋樹(Binay Search Tree,即BST)

如圖,為二叉搜尋樹,它的時間複雜度為O(Log(n))。我們現在要執行搜尋,那麼考慮下最好的情況就是,搜尋0009,也就是搜尋的關鍵字為BST的根節點,讀取一次IO;那麼最壞的情況顯而易見就是樹最深的底層葉子節點(深度為N那麼為N次磁碟IO)。

例如 :要索引0010,那麼要進行四次磁碟IO操作,這已經比其他資料結構少很多次了。

能不能優化,怎麼優化

· 如果優化要優化BST最壞的情況

· 最壞情況是由樹的深度決定的,也就是說它是N階BST,那麼我們要壓縮它

· 壓縮 ?什麼意思? 最近世界盃(以英格蘭為例子),就是把BST(大竹竿克勞奇)變成B-Tree(小土豆魯尼)

左為魯尼,右邊為克勞奇~

2 由之前的思考,引出了B-Tree:

B-Tree---平衡多路查詢樹

· B-Tree是為磁碟等外儲存裝置設計的一種平衡查詢樹。

· B-Tree結構的資料可以讓系統高效的找到資料所在的磁碟塊。為了描述B-Tree,首先定義一條記錄為一個二元組[key, data],key為記錄的鍵值,對應表中的主鍵值,data為一行記錄中除主鍵外的資料。對於不同的記錄,key值互不相同。(可以以python中的字典中的鍵值對理解,鍵唯一且不可改變,就是一個索引的標記,而內容存在data裡面)

一棵m階的B-Tree有如下特性:

每棵樹都有好多屬性概念,這裡加粗的是我們用的,是對我們理解B-Tree有關的。

1. 每個節點最多有m個孩子。

  1. 除了根節點和葉子節點外,其它每個節點至少有Ceil(m/2)個孩子。
  2. 若根節點不是葉子節點,則至少有2個孩子
  3. 所有葉子節點都在同一層,且不包含其它關鍵字資訊
  4. 每個非終端節點包含n個關鍵字資訊(P0,P1,…Pn, k1,…kn)
  5. 關鍵字的個數n滿足:ceil(m/2)-1 <= n <= m-1
  6. ki(i=1,…n)為關鍵字,且關鍵字升序排序。
  7. Pi(i=1,…n)為指向子樹根節點的指標。P(i-1)指向的子樹的所有節點關鍵字均小於ki,但都大於k(i-1)

直接上圖,這就是一個B-Tree,紫色為Key,黃色為data,藍色為指標,仔細看發現會有磁碟塊,結合之前講的磁碟IO操作,可以梳理清楚B-Tree的索引流程

每個節點佔用一個盤塊的磁碟空間,一個節點上有兩個升序排序的關鍵字和三個指向子樹根節點的指標,指標儲存的是子節點所在磁碟塊的地址。兩個關鍵詞劃分成的三個範圍域對應三個指標指向的子樹的資料的範圍域。以根節點為例,關鍵字為17和35,P1指標指向的子樹的資料範圍為小於17,P2指標指向的子樹的資料範圍為17~35,P3指標指向的子樹的資料範圍為大於35。

例子查詢60

模擬查詢關鍵字60的過程:

根據根節點找到磁碟塊1,讀入記憶體。【磁碟I/O操作第1次】比較關鍵字60大於區間(17,35),找到磁碟塊1的指標P3。根據P3指標找到磁碟塊4,讀入記憶體。【磁碟I/O操作第2次】比較關鍵字60小於在區間(65,87),找到磁碟塊3的指標P1。根據P2指標找到磁碟塊9,讀入記憶體。【磁碟I/O操作第3次】在磁碟塊9中的關鍵字列表中找到關鍵字60。分析上面過程,發現需要3次磁碟I/O操作,和3次記憶體查詢操作。由於記憶體中的關鍵字是一個有序表結構,可以利用二分法查詢提高效率。而3次磁碟I/O操作是影響整個B-Tree查詢效率的決定因素。B-Tree使每次磁碟I/O取到記憶體的資料都發揮了作用,從而提高了查詢效率。

!!!:注意比之前BST多了在每一個磁碟頁的索引比較,但是因為磁碟頁已經被磁碟IO讀取操作讀入了記憶體中,所以記憶體IO操作比磁碟IO操作省時很多根本不是一個數量級的可以忽略不計,所以磁碟IO操作仍然是最重要的索引效能關鍵。

顯而易見,這次我們進行了三次磁碟IO比之前的BST少了一次,試想一下在複雜的B-Tree索引中,會減少很多次磁碟IO操作,所以B-Tree更適合索引。

那麼還可以再優化麼?

iphone6/7/8 升級了就是 iphone6+/7+/8+ ,是的加個+(plus),看來都這麼幹。

B+-Tree:

B+Tree是在B-Tree基礎上的一種優化,使其更適合實現外儲存索引結構,InnoDB儲存引擎就是用B+Tree實現其索引結構。

之前我們記得B-Tree的索引和關鍵字key-data對存在磁盤裡面,然後被磁碟IO操作讀入記憶體,那麼在磁碟頁中,如果資料很大呢,如果是大資料呢!!!!

如果資料大的話,磁碟頁無法裝載,會使得一頁的key的數量減少,還是會使得B-Tree的深度增加,這樣還是會增加磁碟IO查詢,電腦科學的老前輩就有一個大膽的想法,那麼我把資料全部放在Tree的葉子節點呢。

在B+Tree中,所有資料記錄節點都是按照鍵值大小順序存放在同一層的葉子節點上,而非葉子節點上只儲存key值資訊,這樣可以大大加大每個節點儲存的key值數量,降低B+Tree的高度。

B+Tree相對於B-Tree有幾點不同:

非葉子節點只儲存鍵值資訊。所有葉子節點之間都有一個鏈指標。資料記錄都存放在葉子節點中。

B+-Tree圖

通常在B+Tree上有兩個頭指標,一個指向根節點,另一個指向關鍵字最小的葉子節點,而且所有葉子節點(即資料節點)之間是一種鏈式環結構。因此可以對B+Tree進行兩種查詢運算:一種是對於主鍵的範圍查詢和分頁查詢,另一種是從根節點開始,進行隨機查詢。

圖中為聚集索引(clustered index),即範圍查詢,從跟節點開始。

!!!B-Tree與B+-Tree的區別!!!

· B+Tree的磁碟讀寫更低,因為非葉子節點可以儲存更多的索引key,而key索引在同一層更集中,那麼會降低磁碟IO讀寫次數。

· B+Tree的查詢效率更穩定,由於最終指向檔案內容的節點是葉子節點中關鍵字的索引,所以任何關鍵只查詢都必須走從根節點到葉子節點的索引路徑,路徑是相似的(深度),所以更穩定(最好最壞情況都在最底層)。

· 區間訪問性友好Mysql是關係型資料庫,所以經常會按照區間來訪問某個索引,B+樹的葉子節點會按順序建立起鏈狀指標,加強區間訪問性。

最後一個問題:

也是最重要的為什麼Mysql會使用B/B+樹來實現索引呢?

· Mysql是基於磁碟的資料庫,索引是以索引檔案的形式轉存於磁碟中的

· 索引的過程就是要涉及磁碟IO的消耗,磁碟IO消耗比記憶體IO消耗要搞好幾個數量級, 即索引涉及的在查詢關鍵字時儘量減少磁碟IO消耗。

附加問題:

作業系統、資料庫、或者說Mysql資料庫引擎為什麼使用B+-Tree的多呢?

· 因為B-Tree仍然未解決關鍵字的遍歷,如果要遍歷B-Tree上的所以關鍵字時間複雜度非常高,但是B+-Tree就不一樣了,可以直接遍歷葉子節點,支援範圍索引,因為OS DB DB ENGINE 都經常使用範圍索引,故 B+-Tree更勝一籌。

打完收工~