B-Tree索引詳解及聯合索引使用

B-Tree索引原理詳解部分轉載自:http://zsuil.com/?p=1184

一.B-tree索引詳解

B-tree索引(或Balanced Tree),是一種很普遍的數據庫索引結構,oracle默認的索引類型(本文也主要依據oracle來講)。其特點是定位高效、利用率高、自我平衡,特別適用於高基數字段,定位單條或小範圍數據非常高效。理論上,使用B-tree在億條數據與100條數據中定位記錄的花銷相同。

數據結構利用率高、定位高效

B-tree索引的數據結構如下:


 

結構看起來B-tree索引與Binary Tree相似,但在細節上有所不同,上圖中用不同顏色的標示出了B-tree索引的幾個主要特點:

  • 樹形結構:由根節(root)、分支(branches)、葉(leaves)三級節點組成,其中分支節點可以有多層。
  • 多分支結構:與binary tree不相同的是,b-tree索引中單root/branch可以有多個子節點(超過2個)。
  • 雙向鏈表:整個葉子節點部分是一個雙向鏈表(後面會描述這個設計的作用)
  • 單個數據塊中包括多條索引記錄

這裏先把幾個特點羅列出來,後面會說到各自的作用。

結構上B-tree與Binary Tree的區別,在於binary中每節點代表一個數值,而balanced中root和B-tree節點中記錄了多條”值範圍”條目(如:[60-70][70-80]),這些”值範圍”條目分別指向在其範圍內的葉子節點。既root與branch可以有多個分支,而不一定是兩個,對數據塊的利用率更高

在Leaf節點中,同樣也是存放了多條索引記錄,這些記錄就是具體的索引列值,和與其對應的rowid。另外,在葉節點層上,所有的節點在組成了一個雙向鏈表。
瞭解基本結構後,下圖展示定位數值82的過程:



演算如下:
讀取root節點,判斷82大於在0-120之間,走左邊分支。
讀取左邊branch節點,判斷82大於80且小於等於120,走右邊分支。
讀取右邊leaf節點,在該節點中找到數據82及對應的rowid
使用rowid去物理表中讀取記錄數據塊(如果是count或者只select rowid,則最後一次讀取不需要)

在整個索引定位過程中,數據塊的讀取只有3次。既三次I/O後定位到rowid。

而由於B-tree索引對結構的利用率很高,定位高效。當1千萬條數據時,B-tree索引也是三層結構(依稀記得億級數據纔是3層與4層的分水嶺)。定位記錄仍只需要三次I/O,這便是開頭所說的,100條數據和1千萬條數據的定位,在b-tree索引中的花銷是一樣的。


平衡擴張

除了利用率高、定位高效外,B-tree的另一個特點是能夠永遠保持平衡,這與它的擴張方式有關。(unbalanced和hotspot是兩類問題,之前我一直混在一起),先描述下B-tree索引的擴張方式:

新建一個索引,索引上只會有一個leaf節點,取名爲Node A,不斷的向這個leaf節點中插入數據後,直到這個節點滿,這個過程如下圖(綠色表示新建/空閒狀態,紅色表示節點沒有空餘空間):


當Node A滿之後,我們再向表中插入一條記錄,此時索引就需要做拆分處理:會新分配兩個數據塊NodeB & C,如果新插入的值,大於當前最大值,則將Node A中的值全部插入Node B中,將新插入的值放到Node C中;否則按照5-5比例,將已有數據分別插入到NodeB與C中。

無論採用哪種分割方式,之前的leaf節點A,將變成一個root節點,保存兩個範圍條目,指向B與C,結構如下圖(按第一種拆分形式):


當Node C滿之後,此時 Node A仍有空餘空間存放條目,所以不需要再拆分,而只是新分配一個數據塊Node D,將在Node A中創建指定到Node D的條目:


如果當根節點Node A也滿了,則需要進一步拆分:新建Node E&F&G,將Node A中範圍條目拆分到E&F兩個節點中,並建立E&F到BCD節點的關聯,向Node G插入索引值。此時E&F爲branch節點,G爲leaf節點,A爲Root節點:

 

 

在整個擴張過程中,B-tree自身總能保持平衡,Leaf節點的深度能一直保持一致


 

實際應用中的一些問題

前面說完了B-tree索引的結構與擴張邏輯,接下來講一些B-tree索引在應用中的一些問題:

單一方向擴展引起的索引競爭(Index Contention)

若索引列使用sequence或者timestamp這類只增不減的數據類型。這種情況下B-tree索引的增長方向總是不變的,不斷的向右邊擴展,因爲新插入的值永遠是最大的。

當一個最大值插入到leaf block中後,leaf block要向上傳播,通知上層節點更新所對應的“值範圍”條目中的最大值,因此所有靠右邊的block(從leaf 到branch甚至root)都需要做更新操作,並且可能因爲塊寫滿後執行塊拆分。

如果併發插入多個最大值,則最右邊索引數據塊的的更新與拆分都會存在爭搶,影響效率。在AWR報告中可以通過檢測enq: TX – index contention事件的時間來評估爭搶的影響。解決此類問題可以使用Reverse Index解決,不過會帶來新的問題。

Index Browning 索引枯萎(不知道該怎麼翻譯這個名詞,就是指leaves節點”死”了,樹枯萎了)

其實oracle針對這個問題有優化機制,但優化的不徹底,所以還是要拿出來的說。

我們知道當表中的數據刪除後,索引上對應的索引值是不會刪除的,特別是在一性次刪除大批量數據後,會造成大量的dead leaf掛到索引樹上。考慮以下示例,如果表100以上的數據會部被刪除了,但這些記錄仍在索引中存在,此時若對該列取max():


通過與之前相同演算,找到了索引樹上最大的數據塊,按照記錄最大的值應該在這裏,但發現這數據塊裏的數據已經被清空了,與是利用B-tree索引的另一個特點:leaves節點是一個雙向列表,若數據沒有找到就去臨近的一個數據塊中看看,在這個數據塊中發現了最大值99。

在計算最大值的過程中,這次的定位多加載了一個數據塊,再極端的情況下,大批量的數據被刪除,就會造成大量訪問這些dead leaves。

針對這個問題的一般解決辦法是重建索引,但記住! 重建索引並不是最優方案,詳細原因可以看看這。使用coalesce語句來整理這些dead leaves到freelist中,就可以避免這些問題。理論上oracle中這步操作是可以自動完成的,但在實際中一次性大量刪除數據後,oracle在短時間內是反應不過來的。

更多資料:

Ask Tom: Possible arcane question on B*Tree indexes as Oracle sees them 有點老了
So When Does An Oracle B-Tree Index Increase In Height
http://docs.oracle.com/cd/E11882_01/server.112/e25789/indexiot.htm#autoId6
http://www.dba-oracle.com/t_index_leaf_block_contention_tuning.htm


二.聯合索引使用規則

    我在此文中測試總結了一部分聯合索引的使用規則,可能並沒有包含所有的規則,畢竟是以一道題來展開的分析。另外如果有這樣一種情況:where c1='1' and c2>'0' and c3<'5' and c4='1';


結果顯示2個字段用到了索引,肯定就是c1,c2字段了。那麼爲什麼c3以後會斷掉呢?

那麼我來大膽的分析一下:

        此索引是B-Tree類型的聯合索引,如果是按照索引字段順序應該是c1,c2...c4順序查詢到的結果應該是從一個分支走到一個數據塊得到結果。但是如果c2>'0'使用了範圍查找,那麼B-Tree樹c1下c2這一級可能會檢索到多個樹的分支,導致c2下c3以及後面的樹延伸分支會更多,多個分支最終對應多個數據塊

        是否是這多個分支對應多個數據塊引起的c2後面的字段無法再使用索引了呢?此時是由於c2範圍查找引起的c3以後的字段無法使用聯合索引。那麼我們在來看一種缺少c2的情況呢: where c1='1' and c4>'0' and c3='1';如下圖:


where中沒有c2的情況下,只有c1字段使用了該索引,這種情況我們是否可以理解爲:B-Tree樹c1下c2的所有分支呢,所有分支其實類似於範圍查詢的多個分支。

        以上結論,只是個人分析,因爲我確實沒有查到聯合索引爲什麼會“斷裂”的原因,“斷裂”一詞也沒有找到更爲專業的形容詞。如果某位大神明確這個原理,請留言指正並附上依據,爲我解惑,先行謝過。

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