1. 程式人生 > >資料庫常見索引解析(B樹,B-樹,B+樹,B*樹,點陣圖索引,Hash索引)

資料庫常見索引解析(B樹,B-樹,B+樹,B*樹,點陣圖索引,Hash索引)

B樹

       即二叉搜尋樹:

       1.所有非葉子結點至多擁有兩個兒子(Left和Right);

       2.所有結點儲存一個關鍵字;

       3.非葉子結點的左指標指向小於其關鍵字的子樹,右指標指向大於其關鍵字的子樹;

       如:

       

       B樹的搜尋,從根結點開始,如果查詢的關鍵字與結點的關鍵字相等,那麼就命中;否則,如果查詢關鍵字比結點關鍵字小,就進入左兒子;如果比結點關鍵字大,就進入右兒子;如果左兒子或右兒子的指標為空,則報告找不到相應的關鍵字;

       如果B樹的所有非葉子結點的左右子樹的結點數目均保持差不多(平衡),那麼B樹的搜尋效能逼近二分查詢;但它比連續記憶體空間的二分查詢的優點是,改變B樹結構(插入與刪除結點)不需要移動大段的記憶體資料,甚至通常是常數開銷;

       如:

      

   但B樹在經過多次插入與刪除後,有可能導致不同的結構:

  

   右邊也是一個B樹,但它的搜尋效能已經是線性的了;同樣的關鍵字集合有可能導致不同的樹結構索引;所以,使用B樹還要考慮儘可能讓B樹保持左圖的結構,和避免右圖的結構,也就是所謂的“平衡”問題;      

       實際使用的B樹都是在原B樹的基礎上加上平衡演算法,即“平衡二叉樹”;如何保持B樹結點分佈均勻的平衡演算法是平衡二叉樹的關鍵;平衡演算法是一種在B樹中插入和刪除結點的策略。常見的平衡二叉樹有:AVL,RBT,Treap,Splay Tree。

B-樹

是一種多路搜尋樹(並不是二叉的):

       1.定義任意非葉子結點最多隻有M個兒子;且M>2;

       2.根結點的兒子數為[2, M];

       3.除根結點以外的非葉子結點的兒子數為[M/2,M];

       4.每個結點存放至少M/2-1(取上整)和至多M-1個關鍵字;(至少2個關鍵字)

       5.非葉子結點的關鍵字個數=指向兒子的指標個數-1;

       6.非葉子結點的關鍵字:K[1], K[2], …, K[M-1];且K[i] < K[i+1];

       7.非葉子結點的指標:P[1], P[2], …, P[M];其中P[1]指向關鍵字小於K[1]的子樹,P[M]指向關鍵字大於K[M-1]的子樹,其它P[i]指向關鍵字屬於(K[i-1], K[i])的子樹;

       8.所有葉子結點位於同一層;

       如:(M=3)


  B-樹的搜尋,從根結點開始,對結點內的關鍵字(有序)序列進行二分查詢,如果命中則結束,否則進入查詢關鍵字所屬範圍的兒子結點;重複,直到所對應的兒子指標為空,或已經是葉子結點;

B-樹的特性:

       1.關鍵字集合分佈在整顆樹中;

       2.任何一個關鍵字出現且只出現在一個結點中;

       3.搜尋有可能在非葉子結點結束;

       4.其搜尋效能等價於在關鍵字全集內做一次二分查詢;

       5.自動層次控制;

       由於限制了除根結點以外的非葉子結點,至少含有M/2個兒子,確保了結點的至少利用率,其最底搜尋效能為:


其中,M為設定的非葉子結點最多子樹個數,N為關鍵字總數;

       所以B-樹的效能總是等價於二分查詢(與M值無關),也就沒有B樹平衡的問題;

       由於M/2的限制,在插入結點時,如果結點已滿,需要將結點分裂為兩個各佔M/2的結點;刪除結點時,需將兩個不足M/2的兄弟結點合併;

B+樹

  B+樹是B-樹的變體,也是一種多路搜尋樹:

       1.其定義基本與B-樹同,除了:

       2.非葉子結點的子樹指標與關鍵字個數相同;

       3.非葉子結點的子樹指標P[i],指向關鍵字值屬於[K[i], K[i+1])的子樹(B-樹是開區間);

       5.為所有葉子結點增加一個鏈指標;

       6.所有關鍵字都在葉子結點出現;

       如:(M=3)


B+的搜尋與B-樹也基本相同,區別是B+樹只有達到葉子結點才命中(B-樹可以在非葉子結點命中),其效能也等價於在關鍵字全集做一次二分查詢;

       B+的特性:

       1.所有關鍵字都出現在葉子結點的連結串列中(稠密索引),且連結串列中的關鍵字恰好是有序的;

       2.不可能在非葉子結點命中;

       3.非葉子結點相當於是葉子結點的索引(稀疏索引),葉子結點相當於是儲存(關鍵字)資料的資料層;

       4.更適合檔案索引系統;


B*樹

是B+樹的變體,在B+樹的非根和非葉子結點再增加指向兄弟的指標;


B*樹定義了非葉子結點關鍵字個數至少為(2/3)*M,即塊的最低使用率為2/3(代替B+樹的1/2);

       B+樹的分裂:當一個結點滿時,分配一個新的結點,並將原結點中1/2的資料複製到新結點,最後在父結點中增加新結點的指標;B+樹的分裂隻影響原結點和父結點,而不會影響兄弟結點,所以它不需要指向兄弟的指標;

       B*樹的分裂:當一個結點滿時,如果它的下一個兄弟結點未滿,那麼將一部分資料移到兄弟結點中,再在原結點插入關鍵字,最後修改父結點中兄弟結點的關鍵字(因為兄弟結點的關鍵字範圍改變了);如果兄弟也滿了,則在原結點與兄弟結點之間增加新結點,並各複製1/3的資料到新結點,最後在父結點增加新結點的指標;

       所以,B*樹分配新結點的概率比B+樹要低,空間使用率更高;

小結

       B樹:二叉樹,每個結點只儲存一個關鍵字,等於則命中,小於走左結點,大於走右結點;

       B-樹:多路搜尋樹,每個結點儲存M/2到M個關鍵字,非葉子結點儲存指向關鍵字範圍的子結點;

       所有關鍵字在整顆樹中出現,且只出現一次,非葉子結點可以命中;

       B+樹:在B-樹基礎上,為葉子結點增加連結串列指標,所有關鍵字都在葉子結點中出現,非葉子結點作為葉子結點的索引;B+樹總是到葉子結點才命中;

       B*樹:在B+樹基礎上,為非葉子結點也增加連結串列指標,將結點的最低利用率從1/2提高到2/3


點陣圖索引

1.案例

有張表名為table的表,由三列組成,分別是姓名、性別和婚姻狀況,其中性別只有男和女兩項,婚姻狀況由已婚、未婚、離婚這三項,該表共有100w個記錄。現在有這樣的查詢:     select * from table where Gender=‘男’ and Marital=“未婚”;

姓名(Name)

性別(Gender)

婚姻狀況(Marital)

張三

已婚

李四

已婚

王五

未婚

趙六

離婚

孫七

未婚

...

...

...

1)不使用索引

  不使用索引時,資料庫只能一行行掃描所有記錄,然後判斷該記錄是否滿足查詢條件。

2)B樹索引

  對於性別,可取值的範圍只有'男','女',並且男和女可能各站該表的50%的資料,這時新增B樹索引還是需要取出一半的資料, 因此完全沒有必要。相反,如果某個欄位的取值範圍很廣,幾乎沒有重複,比如身份證號,此時使用B樹索引較為合適。事實上,當取出的行資料佔用表中大部分的資料時,即使添加了B樹索引,資料庫如oracle、MySQL也不會使用B樹索引,很有可能還是一行行全部掃描。

2.點陣圖索引出馬

如果使用者查詢的列的基數非常的小, 即只有的幾個固定值,如性別、婚姻狀況、行政區等等。要為這些基數值比較小的列建索引,就需要建立點陣圖索引。

對於性別這個列,點陣圖索引形成兩個向量,男向量為10100...,向量的每一位表示該行是否是男,如果是則位1,否為0,同理,女向量位01011。

RowId

1

2

3

4

5

...

1

0

1

0

0

0

1

0

1

1

 ...

  對於婚姻狀況這一列,點陣圖索引生成三個向量,已婚為11000...,未婚為00100...,離婚為00010...。

RowId

1

2

3

4

5

...

已婚

1

1

0

0

0

未婚

0

0

1

0

1

離婚

0

0

0

1

0

   當我們使用查詢語句“select * from table where Gender=‘男’ andMarital=“未婚”;”的時候 首先取出男向量10100...,然後取出未婚向量00100...,將兩個向量做and操作,這時生成新向量00100...,可以發現第三位為1,表示該表的第三行資料就是我們需要查詢的結果。 

RowId

1

2

3

4

5

1

0

1

0

0

and

未婚

0

0

1

0

1

結果

0

0

1

0

0

3.點陣圖索引適應場景

上面講了,點陣圖索引適合只有幾個固定值的列,如性別、婚姻狀況、行政區等等,而身份證號這種型別不適合用點陣圖索引。

  此外,點陣圖索引適合靜態資料,而不適合索引頻繁更新的列。舉個例子,有這樣一個欄位busy,記錄各個機器的繁忙與否,當機器忙碌時,busy1,當機器不忙碌時,busy0

  這個時候有人會說使用點陣圖索引,因為busy只有兩個值。好,我們使用點陣圖索引索引busy欄位!假設使用者A使用update更新某個機器的busy值,比如update table set table.busy=1 where rowid=100;,但還沒有commit,而使用者B也使用update更新另一個機器的busy值,update table set table.busy=1 where rowid=12; 這個時候使用者B怎麼也更新不了,需要等待使用者A commit

  原因:使用者A更新了某個機器的busy值為1,會導致所有busy1的機器的點陣圖向量發生改變,因此資料庫會將busy1的所有行鎖定,只有commit之後才解鎖。


Hash索引

索引列會被儲存在匹配到的hash bucket裡面的表裡,這個表裡會有實際的資料行指標,再根據實際的資料行指標查詢對應的資料行。


概括來說,要查詢一行資料或者處理一個where子句,SQL Server引擎需要做下面幾件事

1、根據where條件裡面的引數生成合適的雜湊函式

2、索引列進行匹配,匹配到對應hash bucket,找到對應hash bucket意味著也找到了對應的資料行指標(row pointer

3、讀取資料

雜湊索引比起B樹索引簡單,因為它不需要遍歷B樹,所以訪問速度會更快


Hash索引的缺點:

1、因為Hash索引比較的是經過Hash計算的值,所以只能進行等式比較,不能用於範圍查詢

2、由於雜湊值是按照順序排列的,但是雜湊值對映的真正資料在雜湊表中就不一定按照順序排列,所以無法利用Hash索引來加速任何排序操作

3、不能用部分索引鍵來搜尋,因為組合索引在計算雜湊值的時候是一起計算的。

4、當雜湊值大量重複且資料量非常大時,其檢索效率並沒有Btree索引高的。