1. 程式人生 > >MySql索引演算法原理解析(通俗易懂,只講B-tree)

MySql索引演算法原理解析(通俗易懂,只講B-tree)

 剛開始學習的時候,百度去查,但發現好多說得太複雜不好理解,結合各個文章總結一下(建議大概看文字,不理解不要緊,然後再看圖的執行步驟然後在結合文字,這樣一切就清晰好多)

    B-tree,B是balance,一般用於資料庫的索引使用B-tree結構可以顯著減少定位記錄時所經歷的中間過程,從而加快存取速度。而B+tree是B-tree的一個變種,大名鼎鼎的MySQL就普遍使用B+tree實現其索引結構。

  那資料庫為什麼使用這種結構?

  一般來說,索引本身也很大,不可能全部儲存在記憶體中,因此索引往往以索引檔案的形式儲存的磁碟上。這樣的話,索引查詢過程中就要產生磁碟I/O消耗,相對於記憶體存取,I/O存取的消耗要高几個數量級,所以評價一個數據結構作為索引的優劣最重要的指標就是在查詢過程中磁碟I/O操作次數的漸進複雜度。換句話說,索引的結構組織要儘量減少查詢過程中磁碟I/O的存取次數。

  為了達到這個目的,磁碟按需讀取,要求每次都會預讀的長度一般為頁的整數倍。而且資料庫系統將一個節點的大小設為等於一個頁,這樣每個節點只需要一次I/O就可以完全載入。每次新建節點時,直接申請一個頁的空間,這樣就保證一個節點物理上也儲存在一個頁裡,加之計算機儲存分配都是按頁對齊的,就實現了一個node只需一次I/O。並把B-tree中的m值設的非常大,就會讓樹的高度降低,有利於一次完全載入

m-way查詢樹(重點看步驟圖

  首先介紹一下m-way查詢樹,顧名思義就是一棵樹的每個節點的度小於等於m。

  故,它的性質如下:

  1. 每個節點的鍵值數小於m
  2. 每個節點的度小於等於m
  3. 鍵值按順序排列
  4. 子樹的鍵值要完全小於或大於或介於父節點之間的鍵值

B-tree

B-tree又叫平衡多路查詢樹。一棵m階的B-tree (m叉樹)的特性如下:

(其中ceil(x)是一個取上限的函式)

1)  樹中每個結點至多有m個孩子;

3)  若根結點不是葉子結點,則至少有2個孩子(特殊情況:沒有孩子的根結點,即根結點為葉子結點,整棵樹只有一個根節點);

4)  所有葉子結點都出現在同一層,葉子結點不包含任何關鍵字資訊(可以看做是外部結點或查詢失敗的結點,實際上這些結點不存在,指向這些結點的指標都為null)

5)  每個非終端結點中包含有n個關鍵字資訊: (nP0K1P1K2P2......KnPn)。其中:

a)   Ki (i=1...n)為關鍵字,且關鍵字按順序排序

K(i-1)< Ki

b)   Pi為指向子樹根的接點,且指標P(i-1)指向子樹種所有結點的關鍵字均小於Ki,但都大於K(i-1)

      c)   關鍵字的個數n必須滿足: ceil(m / 2)-1 <= n <= m-1

B-tree中的每個結點根據實際情況可以包含大量的關鍵字資訊和分支(當然是不能超過磁碟塊的大小,根據磁碟驅動(disk drives)的不同,一般塊的大小在1k~4k左右);這樣樹的深度降低了,這就意味著查詢一個元素只要很少結點從外存磁碟中讀入記憶體,很快訪問到要查詢的資料。

下面以一棵5階B-tree例項進行講解(如下圖所示):(重點看以下圖

其滿足上述條件:除根結點和葉子結點外,其它每個結點至少有ceil(5/2)=3個孩子(至少2個關鍵字);當然最多5個孩子(最多4個關鍵字)。下圖中關鍵字為大寫字母,順序為字母升序。

插入(insert)操作插入一個元素時,首先在B-tree中是否存在,如果不存在,即在葉子結點處結束,然後在葉子結點中插入該新的元素,注意:如果葉子結點空間足夠,這裡需要向右移動該葉子結點中大於新插入關鍵字的元素,如果空間滿了以致沒有足夠的空間去新增新的元素,則將該結點進行“分裂”,將一半數量的關鍵字元素分裂到新的其相鄰右結點中,中間關鍵字元素上移到父結點中(當然,如果父結點空間滿了,也同樣需要“分裂”操作),而且當結點中關鍵元素向右移動了,相關的指標也需要向右移。如果在根結點插入新元素,空間滿了,則進行分裂操作,這樣原來的根結點中的中間關鍵字元素向上移動到新的根結點中,因此導致樹的高度增加一層。

咱們通過一個例項來逐步講解下。插入以下字元字母到空的5階B-tree中:C N G A H E K Q M F W L T Z D P R X Y S5序意味著一個結點最多有5個孩子和4個關鍵字,除根結點外其他結點至少有2個關鍵字,首先,結點空間足夠,4個字母插入相同的結點中,如下圖:

當咱們試著插入H時,結點發現空間不夠,以致將其分裂成2個結點,移動中間元素G上移到新的根結點中,在實現過程中,咱們把AC留在當前結點中,而HN放置新的其右鄰居結點中。如下圖:

當咱們插入E,K,Q時,不需要任何分裂操作

插入M需要一次分裂,注意M恰好是中間關鍵字元素,以致向上移到父節點中

插入F,W,L,T不需要任何分裂操作

插入Z時,最右的葉子結點空間滿了,需要進行分裂操作,中間元素T上移到父節點中,注意通過上移中間元素,樹最終還是保持平衡,分裂結果的結點存在2個關鍵字元素。

插入D時,導致最左邊的葉子結點被分裂,D恰好也是中間元素,上移到父節點中,然後字母P,R,X,Y陸續插入不需要任何分裂操作。

最後,當插入S時,含有N,P,Q,R的結點需要分裂,把中間元素Q上移到父節點中,但是情況來了,父節點中空間已經滿了,所以也要進行分裂,將父節點中的中間元素M上移到新形成的根結點中,注意以前在父節點中的第三個指標在修改後包括DG節點中。這樣具體插入操作的完成,下面介紹刪除操作,刪除操作相對於插入操作要考慮的情況多點。

刪除(delete)操作:首先查詢B-tree中需刪除的元素,如果該元素在B-tree中存在,則將該元素在其結點中進行刪除,如果刪除該元素後,首先判斷該元素是否有左右孩子結點,如果有,則上移孩子結點中的某相近元素到父節點中,然後是移動之後的情況;如果沒有,直接刪除後,移動之後的情況.。

刪除元素,移動相應元素之後,如果某結點中元素數目小於ceil(m/2)-1,則需要看其某相鄰兄弟結點是否豐滿(結點中元素個數大於ceil(m/2)-1),如果豐滿,則向父節點借一個元素來滿足條件;如果其相鄰兄弟都剛脫貧,即借了之後其結點數目小於ceil(m/2)-1,則該結點與其相鄰的某一兄弟結點進行合併成一個結點,以此來滿足條件。那咱們通過下面例項來詳細瞭解吧。

以上述插入操作構造的一棵5階B-tree為例,依次刪除H,T,R,E

首先刪除元素H,當然首先查詢HH在一個葉子結點中,且該葉子結點元素數目3大於最小元素數目ceil(m/2)-1=2,則操作很簡單,咱們只需要移動K至原來H的位置,移動LK的位置(也就是結點中刪除元素後面的元素向前移動)

下一步,刪除T,因為T沒有在葉子結點中,而是在中間結點中找到,咱們發現他的繼承者W(字母升序的下個元素),將W上移到T的位置,然後將原包含W的孩子結點中的W進行刪除,這裡恰好刪除W後,該孩子結點中元素個數大於2,無需進行合併操作。

下一步刪除RR在葉子結點中,但是該結點中元素數目為2,刪除導致只有1個元素,已經小於最小元素數目ceil(5/2)-1=2,如果其某個相鄰兄弟結點中比較豐滿(元素個數大於ceil(5/2)-1=2),則可以向父結點借一個元素,然後將最豐滿的相鄰兄弟結點中上移最後或最前一個元素到父節點中,在這個例項中,右相鄰兄弟結點中比較豐滿(3個元素大於2),所以先向父節點借一個元素W下移到該葉子結點中,代替原來S的位置,S前移;然後X在相鄰右兄弟結點中上移到父結點中,最後在相鄰右兄弟結點中刪除X,後面元素前移。

最後一步刪除E刪除後會導致很多問題,因為E所在的結點數目剛好達標,剛好滿足最小元素個數(ceil(5/2)-1=2,而相鄰的兄弟結點也是同樣的情況,刪除一個元素都不能滿足條件,所以需要該節點與某相鄰兄弟結點進行合併操作;首先移動父結點中的元素(該元素在兩個需要合併的兩個結點元素之間)下移到其子結點中,然後將這兩個結點進行合併成一個結點。所以在該例項中,咱們首先將父節點中的元素D下移到已經刪除E而只有F的結點中,然後將含有DF的結點和含有A,C的相鄰兄弟結點進行合併成一個結點。

也許你認為這樣刪除操作已經結束了,其實不然,在看看上圖,對於這種特殊情況,你立即會發現父節點只包含一個元素G,沒達標,這是不能夠接受的。如果這個問題結點的相鄰兄弟比較豐滿,則可以向父結點借一個元素。假設這時右兄弟結點(含有Q,X)有一個以上的元素(Q右邊還有元素),然後咱們將M下移到元素很少的子結點中,將Q上移到M的位置,這時,Q的左子樹將變成M的右子樹,也就是含有NP結點被依附在M的右指標上。所以在這個例項中,咱們沒有辦法去借一個元素,只能與兄弟結點進行合併成一個結點,而根結點中的唯一元素M下移到子結點,這樣,樹的高度減少一層。

為了進一步詳細討論刪除的情況。再舉另外一個例項:

這裡是一棵不同的5階B-tree,那咱們試著刪除C

於是將刪除元素C的右子結點中的D元素上移到C的位置,但是出現上移元素後,只有一個元素的結點的情況。

又因為含有E的結點,其相鄰兄弟結點才剛脫貧(最少元素個數為2),不可能向父節點借元素,所以只能進行合併操作,於是這裡將含有A,B的左兄弟結點和含有E的結點進行合併成一個結點。

這樣又出現只含有一個元素F結點的情況,這時,其相鄰的兄弟結點是豐滿的(元素個數為3>最小元素個數2),這樣就可以想父結點借元素了,把父結點中的J下移到該結點中,相應的如果結點中J後有元素則前移,然後相鄰兄弟結點中的第一個元素(或者最後一個元素)上移到父節點中,後面的元素(或者前面的元素)前移(或者後移);注意含有KL的結點以前依附在M的左邊,現在變為依附在J的右邊。這樣每個結點都滿足B-tree結構性質。