MySQL索引-(B-Tree和B+Tree學習)

B-tree

B樹的出現是爲了彌合不同的存儲級別之間的訪問速度上的巨大差異,實現高效的 I/O。平衡二叉樹的查找效率是非常高的,並可以通過降低樹的深度來提高查找的效率。但是當數據量非常大,樹的存儲的元素數量是有限的,這樣會導致二叉查找樹結構由於樹的深度過大而造成磁盤I/O讀寫過於頻繁,進而導致查詢效率低下。另外數據量過大會導致內存空間不夠容納平衡二叉樹所有節點的情況。B樹是解決這個問題的很好的結構

B樹又稱多路平衡查找樹,B樹中所有節點的孩子節點數的最大值稱爲B樹的階。

一顆m階的B樹定義如下:

  • 每個節點最多有m個子樹
  • 每個節點最多有m-1個關鍵字
  • 根節點最少有1個關鍵字
  • 非根節點至少有Math.ceil(m/2)-1個關鍵字
  • 所有葉子節點都位於同一層,或者說根節點到每個葉子節點的長度都相同
  • 每個節點中的關鍵字都按照從小到大的順序排列,每個關鍵字的左子樹中的所有關鍵字都小於它,而右子樹中的所有關鍵字都大於它

什麼是B樹的階

B樹中一個節點的子節點數目的最大值,用m表示,假如最大值爲10,則爲10階,如圖:

所有節點中,節點[13,16,19]擁有的子節點數目最多(4個子節點),所有可以定義上面的圖片爲4階B樹。

B-tree插入

針對m階高度h的B樹,插入一個元素是,首先在B樹中是否存在,如果不存在,即在葉子節點處結束,然後再葉子節點中插入該新的元素。

插入規則:

  • 若該節點元素個數小於m-1,直接插入
  • 若該節點元素個數等於m-1,引起節點分裂;以該節點中間元素爲分界,取中間元素(偶數個數,中間兩個隨機選取)插入到父節點中;
  • 重複上面動作,直到所有節點符合B樹的規則;最壞的情況一直分裂到根節點,生成新的根節點,高度增加1;

上面的規則是插入動作的核心,接下來以5階樹爲例,詳細講解插入的動作:

下面演示下列數字的插入5階B-tree樹步驟(觀看網站):

插入 1 ,3,7,14 如下圖:

b-tree--插入-001

**插入8時,**此時根節點元素個數爲5,不符合1<=根節點元素個數<=m-1,提取中間節點7進行分裂。

b-tree--插入-001

插入5,11,17時不需要任何分裂操作

b-tree--插入-003

**插入元素 13 ,**由於13大於根節點7,向右子節點添加,又由於右節點已經有8,11,14,17四個節點,添加13就要進行分裂。提取中間元素13,插入到父節點中,插入過程如下圖:

b-tree--插入-004

插入元素6,12,20,23時,不需要進行分裂

b-tree--插入-005

**插入26時,**26大於13應該添加到最右的葉子節點中,由於最右的葉子節點【14,17,20,23】空間已經滿了,要進行分裂。

b-tree--插入-006

**插入4時,**導致最左邊葉子節點進行分裂 【1,3,5,6】

b-tree--插入-007

插入24,25時不進行分裂

b-tree--插入-008

**插入21時,**含有【23,24,25,26】的節點需要分裂,把中間元素24上移到父節點中,但是父節點中空間已經滿了,所以也要進行分裂,將父節點的中間元素【13】上移到形成新的根節點。 009

插入元素27不進行任何分裂

插入元素15,16不進行任何分裂

插入元素18分裂

總結B-tree爲:

B-tree刪除

首先查找B樹中需刪除的元素,如果該元素在B樹中存在,則將該元素在其節點中進行刪除;

刪除步驟如下:

  1. 刪除該元素後,首先判斷該元素是否有左右孩子節點,如果有,則上移孩子節點中的某相近元素(“左孩子最右邊的節點”或“右孩子最左邊的節點”)到父節點中(左右孩子節點都可以,如果上移左孩子節點發現左節點個數少於Math.ceil(m/2)-1則上移右孩子節點),然後是移動之後的情況;如果沒有,直接刪除。

  2. 某節點中元素數目小於(m/2)-1,(m/2)向上取整,則需要看其某相鄰兄弟節點是否豐滿;

  3. 如果豐滿(節點中元素個數大於(m/2)-1),則向父節點借一個元素來滿足條件;

  4. 如果其相鄰兄弟都不豐滿,即其節點數目等於(m/2)-1,則該節點與其相鄰的某一兄弟節點進行“合併”成一個節點。合併步驟:首先移動父節點中的元素(該元素在兩個需要合併的兩個節點元素之間)下移到其子節點中,然後將這兩個節點進行合併併成一個節點。

下面我們以上5階B樹爲例來看一下刪除的動作:

【1,3,7,14,8,5,11,17,13,6,12,20,23,26,4,24,25,21,27,15,16,18】

關鍵要領,元素個數小於2 【(m-1)/2-1】就合併,大於 4 【m-1】就分裂

**刪除元素8,**首先查找8的位置,發現8在【8,11,12】葉子節點處,刪除8後該葉子節點元素個數爲2,符合B樹規則,直接刪除。

**刪除元素24,**因爲24在中間節點中,刪除24之後,左孩子節點23上移,發現左孩子節點只剩一個元素(小於兩個元素),而右孩子節點個數爲3(大於ceil(5/2)-1),上移右孩子25,此時左右孩子節點都剩兩個元素(不小於 ceil(5/2)-1),刪除完成

刪除-002

**刪除元素25,**刪除25節點之後,左孩子節點23上移,發現左孩子節點只剩一個元素(小於ceil(5/2)-1),而右孩子節點個數爲2(不大於ceil(5/2)-1)。則進行合併。

**刪除元素5,**因爲5所在的節點數目剛好滿足(ceil(5/2)-1),而相鄰的兄弟節點個數都爲2(等於ceil(5/2)-1);則進行合併,合併之後如下圖:

但是此時刪除操作還沒喲結束,此時元素7所在的節點個數爲一(不符合非根節點元素K必須滿足於ceil(5/2)-1=<K<=m-1 也就是2<=k<=4的定義);如果這個問題節點的相鄰兄弟比較豐滿,則可以向父節點借一個元素,但此時兄弟節點元素個數剛好爲兩個,只能進行合併。而根結點中的唯一元素【13】下移到子結點,這樣,樹的高度減少一層。

B+tree

B+tree是B-tree的變種,有着比B-tree更高的查詢性能:

  1. 有m個子樹的節點包含有m個元素(B-tree是m-1)
  2. 只有葉子結點保存數據,其他節點都只用於索引
  3. 根節點和所有分支節點都同時存在於葉子結點中,在子節點元素中是最大或者最小的
  4. 葉子節點會包含所有的關鍵字,以及指向數據記錄的指針,並且葉子節點本身是根據關鍵字的從小到大順序鏈接

B+tree插入

B+tree的插入必須保證插入後葉子節點中的記錄依然排序,同時需要考慮插入到B+tree的三種情況,每種情況都可能會導致不同的插入算法 。

B+tree插入3種情況

B+Tree演示地址

依次插入【5,10,25,30,50,55,75,80,85,90,15,20,60,65】

b+tree--刪除-001

最後結果爲下圖:

**插入70,**此時原先的Leaf Page已經滿了,但是Index Page還沒有滿,符合上表中的第二種情況,這時插入Leaf Page後的情況爲 【50,55,60,65,70】,並根據中間的60來拆分葉子節點。

**插入95,**這時符合上表中第三種情況,即Leaf Page和Index Page都滿了,這時需要做兩次拆分。

最終結構如下圖:

可以看到,不管怎麼變化,B+tree總是會保持平衡。但是爲了保持平衡對於新插入的鍵值可能需要做大量的拆分頁操作。

因爲B+tree樹結構主要用於磁盤,也的拆分意味着磁盤的操作,所以應該在有可能的情況下儘量減少也的拆分操作。因此,B+樹同樣提供了類似於平衡二叉樹的旋轉(Rotation)功能。旋轉發生在Leaf Page已經滿,但是其左右兄弟節點沒有滿的情況下。這時B+tree並不急於去做拆分也的操作,而是將記錄移到所在頁的兄弟節點上。因此再來看插入70的情況,若插入70,其實B+tree並不急於去拆分葉子節點,而是去做旋轉操作,得到如下圖:

插入70前

插入70前

做拆分

做旋轉

B+tree刪除操作

B+tree樹使用填充因子(fill factor)來控制樹的刪除變化。50%即(m/2)是填充因子可設的最小值。

**刪除元素70,**該記錄符合上表中第一種情況,可以直接刪除

**刪除55,**首先【50,55】節點只剩50,需要和【25,30】節點進行合併(合併規則和B-tree一樣)。此時圖如下:

這個時候刪除還沒有結束,由於【25】節點只剩一個元素,需要和右節點進行合併。最終過程如下:

刪除80,此時符合上表中第二種情況:

最終如下圖:

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章