說(shuō)起數(shù)據(jù)庫(kù),避免不了的要講索引。要真正理解索引,首先就得清楚B+樹(shù)的結(jié)構(gòu)等
B樹(shù)
B樹(shù)即B-樹(shù),而不是兩種樹(shù)。
概念:一棵m階B樹(shù)是一棵平衡的m路搜索樹(shù)。
特點(diǎn):
- m即所有節(jié)點(diǎn)中孩子節(jié)點(diǎn)個(gè)數(shù)的最大值
- 每個(gè)非根節(jié)點(diǎn)所包含的關(guān)鍵字個(gè)數(shù)j滿(mǎn)足:ceil(m/2) - 1 <= j <= m - 1(ceil為向上取整,即大于該數(shù)的最小整數(shù))
- 節(jié)點(diǎn)的子節(jié)點(diǎn)數(shù)會(huì)比關(guān)鍵字個(gè)數(shù)加1
- 根據(jù)以上兩條可以得出,每個(gè)節(jié)點(diǎn)最多有m個(gè)分支,非根非葉節(jié)點(diǎn)至少有ceil(m/2)個(gè)分支
B樹(shù)的查找
B樹(shù)的查找還是很簡(jiǎn)單的,不再贅述。
其最低搜索性能:
B樹(shù)的插入
例:用1,2,6,7,11,4,8,13,10,5,17,9,16,20,3,12,14,18,19,15構(gòu)建5階B樹(shù)
因?yàn)闃?gòu)建5階的B樹(shù),所以每個(gè)節(jié)點(diǎn)的關(guān)鍵字個(gè)數(shù)范圍為[2,4]
插入11時(shí),該節(jié)點(diǎn)的關(guān)鍵字個(gè)數(shù)超出范圍,進(jìn)行分裂
之后直接插入4,8,13
當(dāng)插入10時(shí),節(jié)點(diǎn)關(guān)鍵字個(gè)數(shù)再次超出范圍
將子節(jié)點(diǎn)分裂
直接插入5,17,9,16,插入20
關(guān)鍵字個(gè)數(shù)超出范圍,進(jìn)行分裂
繼續(xù)插入3
關(guān)鍵字個(gè)數(shù)超出范圍,進(jìn)行分裂
繼續(xù)插入15
關(guān)鍵個(gè)數(shù)超出范圍,進(jìn)行分裂
這時(shí)候根節(jié)點(diǎn)關(guān)鍵字個(gè)數(shù)也超出范圍,繼續(xù)分裂
B樹(shù)的刪除
對(duì)于B-樹(shù)關(guān)鍵字的刪除,需要找到待刪除的關(guān)鍵字,在結(jié)點(diǎn)中刪除關(guān)鍵字的過(guò)程也有可能破壞B-樹(shù)的特性,如舊關(guān)鍵字的刪除可能使得結(jié)點(diǎn)中關(guān)鍵字的個(gè)數(shù)少于規(guī)定個(gè)數(shù),這是可能需要向其兄弟結(jié)點(diǎn)借關(guān)鍵字或者和其孩子結(jié)點(diǎn)進(jìn)行關(guān)鍵字的交換,也可能需要進(jìn)行結(jié)點(diǎn)的合并,其中,和當(dāng)前結(jié)點(diǎn)的孩子進(jìn)行關(guān)鍵字交換的操作可以保證刪除操作總是發(fā)生在終端結(jié)點(diǎn)上。
從上面已經(jīng)構(gòu)建好的B樹(shù)中依次刪除8,16,15,4
刪除8:關(guān)鍵字8在葉子節(jié)點(diǎn)上,并且刪除后其所在結(jié)點(diǎn)中關(guān)鍵字的個(gè)數(shù)不會(huì)少于2,因此可以直接刪除。
刪除16:關(guān)鍵字16不在終端結(jié)點(diǎn)上,但是可以用17來(lái)覆蓋16,然后將原來(lái)的17刪除掉
刪除15:15雖然也在終端結(jié)點(diǎn)上,但是不能直接刪除,因?yàn)閯h除后當(dāng)前結(jié)點(diǎn)中關(guān)鍵字的個(gè)數(shù)小于2。這時(shí)需要向其兄弟結(jié)點(diǎn)借關(guān)鍵字,顯然應(yīng)該向其右兄弟來(lái)借關(guān)鍵字,因?yàn)樽笮值艿年P(guān)鍵字個(gè)數(shù)已經(jīng)是下限2.借關(guān)鍵字不能直接將18移到15所在的結(jié)點(diǎn)上,因?yàn)檫@樣會(huì)使得15所在的結(jié)點(diǎn)上出現(xiàn)比17大的關(guān)鍵字,所以正確的借法應(yīng)該是先用17覆蓋15,在用18覆蓋原來(lái)的17,最后刪除原來(lái)的18
刪除4:4在葉子節(jié)點(diǎn)上,但是此時(shí)4所在的節(jié)點(diǎn)的關(guān)鍵字個(gè)數(shù)已經(jīng)到下限,需要借關(guān)鍵字,不過(guò)可以看到其左右兄弟節(jié)點(diǎn)已經(jīng)沒(méi)有多余的關(guān)鍵字可借。所以就需要進(jìn)行關(guān)鍵字的合并??梢韵葘㈥P(guān)鍵字4刪除,然后將關(guān)鍵字5、6、7、9進(jìn)行合并作為一個(gè)節(jié)點(diǎn)鏈接在關(guān)鍵字3右邊的指針上,也可以將關(guān)鍵字1、2、3、5合并作為一個(gè)節(jié)點(diǎn)鏈接在關(guān)鍵字6左邊的指針上
顯然上述兩種情況下都不滿(mǎn)足B-樹(shù)的規(guī)定,即出現(xiàn)了非根的雙分支節(jié)點(diǎn),需要繼續(xù)進(jìn)行合并
B+樹(shù)
一棵m階B+樹(shù)是一棵平衡的m路搜索樹(shù)
- 有k個(gè)子樹(shù)的中間節(jié)點(diǎn)包含有k個(gè)元素(B樹(shù)中是k-1個(gè)元素),每個(gè)元素不保存數(shù)據(jù),只用來(lái)索引,所有數(shù)據(jù)都保存在葉子節(jié)點(diǎn)。
- ceil(m/2) <= j <= m
- 所有的葉子節(jié)點(diǎn)中包含了全部元素的信息,及指向含這些元素記錄的指針,且葉子結(jié)點(diǎn)本身依關(guān)鍵字的大小自小而大順序鏈接(葉子節(jié)點(diǎn)之間增加了橫向的指針)。
- 所有的中間節(jié)點(diǎn)元素都同時(shí)存在于子節(jié)點(diǎn),在子節(jié)點(diǎn)元素中是最大(或最?。┰亍!驹谶@一點(diǎn)上有一些不同的說(shuō)法,其中一種說(shuō)法為,非葉子結(jié)點(diǎn)的子樹(shù)指針與關(guān)鍵字個(gè)數(shù)相同,非葉子結(jié)點(diǎn)的子樹(shù)指針P[i],指向關(guān)鍵字值屬于[K[i], K[i+1])的子樹(shù)(B-樹(shù)是開(kāi)區(qū)間)】
按不同的說(shuō)法,以上兩種形式的B+樹(shù)應(yīng)該都對(duì)
B+的搜索與B-樹(shù)也基本相同,區(qū)別是B+樹(shù)只有達(dá)到葉子結(jié)點(diǎn)才命中(B-樹(shù)可以在非葉子結(jié)點(diǎn)命中),其性能也等價(jià)于在關(guān)鍵字全集做一次二分查找;
B+樹(shù)的查詢(xún)
B+樹(shù)的好處就體現(xiàn)在查詢(xún)性能上:
- 在單元素查詢(xún)的時(shí)候,B+樹(shù)會(huì)自頂向下逐層查找節(jié)點(diǎn),最終找到匹配的葉子節(jié)點(diǎn)。比如拿上面的B+樹(shù)來(lái)進(jìn)行查詢(xún):
現(xiàn)在要查找3:
第一次磁盤(pán)IO:
第二次磁盤(pán)IO:
第三次磁盤(pán)IO:
看到這里,可能會(huì)覺(jué)得查詢(xún)流程和B-樹(shù)差不多呀。
不,有兩點(diǎn)不同。首先,B+樹(shù)的中間節(jié)點(diǎn)沒(méi)有數(shù)據(jù),只存儲(chǔ)索引,所以同樣大小的磁盤(pán)頁(yè)可以容納更大的節(jié)點(diǎn)元素。這就意味著,數(shù)據(jù)量相同的情況下,B+樹(shù)比B-樹(shù)更加“矮胖”,因此查詢(xún)時(shí)IO次數(shù)也更少。
其次,B+樹(shù)的查詢(xún)必須最終查詢(xún)到葉子節(jié)點(diǎn),而B(niǎo)-樹(shù)只要找到匹配元素即可,無(wú)論匹配元素在葉子節(jié)點(diǎn)還是在中間節(jié)點(diǎn)。因此,B-樹(shù)的查找性能并不穩(wěn)定(最好的情況只查找根節(jié)點(diǎn),最壞的情況查找到葉子節(jié)點(diǎn))。而B(niǎo)+樹(shù)的每一次查找都是穩(wěn)定的。
B+樹(shù)查詢(xún)的優(yōu)勢(shì)更多的體現(xiàn)在范圍查詢(xún)上。比如現(xiàn)在要查詢(xún)3-11的元素。
B-樹(shù)的查找過(guò)程:
自頂向下查找到范圍的下限3:
中序遍歷到元素6:
中序遍歷到元素8:
中序遍歷到元素9:
中序遍歷到11,遍歷結(jié)束:
B-樹(shù)的范圍查詢(xún)是不是很繁瑣呢?
再來(lái)看看B+樹(shù)的范圍查詢(xún)。
自頂向下查找到范圍的下限3:
通過(guò)鏈表指針,遍歷到元素6,8:
通過(guò)鏈表指針,遍歷到元素9,11,遍歷結(jié)束:
總結(jié)一下B+樹(shù)的特征和優(yōu)勢(shì)
B+樹(shù)的特征:
1、有k個(gè)子樹(shù)的中間節(jié)點(diǎn)包含有k個(gè)元素(B樹(shù)中是k-1個(gè)元素),每個(gè)元素不保存數(shù)據(jù),只用來(lái)索引,所有元素都保存在葉子節(jié)點(diǎn)。
2、所有的葉子節(jié)點(diǎn)中保存了全部元素的信息,及指向含這些元素記錄的指針,且葉子節(jié)點(diǎn)本身依關(guān)鍵字的大小自小而大順序鏈接。
3、所有的中間節(jié)點(diǎn)元素都同時(shí)存在于子節(jié)點(diǎn),在子節(jié)點(diǎn)元素節(jié)點(diǎn)中是最大(或最?。┰?。
B+樹(shù)的優(yōu)勢(shì):
1、單一節(jié)點(diǎn)存儲(chǔ)更多的元素,使得查詢(xún)的IO次數(shù)更少。
在數(shù)據(jù)庫(kù)檢索來(lái)說(shuō),對(duì)于磁盤(pán)IO掃描時(shí)最消耗時(shí)間的,因?yàn)榇疟P(pán)掃描涉及很多物理特性,這些是相當(dāng)消耗時(shí)間的。所以B+樹(shù)設(shè)計(jì)的初衷就是最大限度的減少對(duì)于磁盤(pán)的掃描次數(shù)。如果一個(gè)表或索引沒(méi)有使用B+樹(shù)(對(duì)于沒(méi)有聚集索引的表是用堆heap存儲(chǔ)),那么查找一個(gè)數(shù)據(jù),需要在整個(gè)表包含的數(shù)據(jù)庫(kù)頁(yè)中全盤(pán)掃描。這無(wú)疑會(huì)大大加重IO負(fù)擔(dān),而使用B+樹(shù)進(jìn)行存儲(chǔ),則僅僅需要將B+樹(shù)的根節(jié)點(diǎn)存入內(nèi)存中,經(jīng)過(guò)幾次查找后就可以找到存放需要數(shù)據(jù)的被葉子結(jié)點(diǎn)包含的頁(yè),進(jìn)而避免了全盤(pán)掃描從而提高了性能。
2、所有查詢(xún)都要查詢(xún)到葉子節(jié)點(diǎn),查詢(xún)性能更穩(wěn)定。
3、所有葉子節(jié)點(diǎn)形成有序鏈表,便于范圍查詢(xún)。
B+樹(shù)的插入
1)若為空樹(shù),創(chuàng)建一個(gè)葉子結(jié)點(diǎn),然后將記錄插入其中,此時(shí)這個(gè)葉子結(jié)點(diǎn)也是根結(jié)點(diǎn),插入操作結(jié)束。
2)針對(duì)葉子類(lèi)型結(jié)點(diǎn):根據(jù)key值找到葉子結(jié)點(diǎn),向這個(gè)葉子結(jié)點(diǎn)插入記錄。插入后,若當(dāng)前結(jié)點(diǎn)key的個(gè)數(shù)小于等于m-1,則插入結(jié)束。否則將這個(gè)葉子結(jié)點(diǎn)分裂成左右兩個(gè)葉子結(jié)點(diǎn),左葉子結(jié)點(diǎn)包含前m/2+1個(gè)記錄,右結(jié)點(diǎn)包含剩下的記錄,將第m/2+1個(gè)記錄的key進(jìn)位到父結(jié)點(diǎn)中(父結(jié)點(diǎn)一定是索引類(lèi)型結(jié)點(diǎn)),進(jìn)位到父結(jié)點(diǎn)的key左孩子指針向左結(jié)點(diǎn),右孩子指針向右結(jié)點(diǎn)。將當(dāng)前結(jié)點(diǎn)的指針指向父結(jié)點(diǎn),然后執(zhí)行第3步。
3)針對(duì)索引類(lèi)型結(jié)點(diǎn):若當(dāng)前結(jié)點(diǎn)key的個(gè)數(shù)小于等于m-1,則插入結(jié)束。否則,將這個(gè)索引類(lèi)型結(jié)點(diǎn)分裂成兩個(gè)索引結(jié)點(diǎn),左索引結(jié)點(diǎn)包含前(m-1)/2個(gè)key,右結(jié)點(diǎn)包含m-(m-1)/2個(gè)key,將第m/2個(gè)key進(jìn)位到父結(jié)點(diǎn)中,進(jìn)位到父結(jié)點(diǎn)的key左孩子指向左結(jié)點(diǎn), 進(jìn)位到父結(jié)點(diǎn)的key右孩子指向右結(jié)點(diǎn)。將當(dāng)前結(jié)點(diǎn)的指針指向父結(jié)點(diǎn),然后重復(fù)第3步。
往下圖的3階B+樹(shù)中插入9
首先查找9應(yīng)插入的葉節(jié)點(diǎn),插入發(fā)現(xiàn)沒(méi)有破壞B+樹(shù)的性質(zhì),插入完畢。
往下圖的3階B+樹(shù)中插入20
首先查找20應(yīng)插入的葉節(jié)點(diǎn),插入:
發(fā)現(xiàn)第二個(gè)葉子節(jié)點(diǎn)已經(jīng)破壞了B+樹(shù)的性質(zhì),則分解成[20,21],[37,44]兩個(gè),并把21往父節(jié)點(diǎn)移。
發(fā)現(xiàn)父節(jié)點(diǎn)也破壞了B+樹(shù)的性質(zhì),則把父節(jié)點(diǎn)再分解成[15,21],[44,59],并把21往父節(jié)點(diǎn)移。
往下圖的3階B+樹(shù)插入100
首先查找100應(yīng)插入的節(jié)點(diǎn),插入
修改其所有父節(jié)點(diǎn)的的鍵值為100(只有插入比當(dāng)前樹(shù)的最大數(shù)大的數(shù)時(shí)要做此步驟)
拆分節(jié)點(diǎn)
B+樹(shù)的刪除
刪除下圖3階B+樹(shù)的關(guān)鍵字91
首先找到91所在的節(jié)點(diǎn),刪除,沒(méi)有破壞B+樹(shù)的性質(zhì),完畢
刪除下圖3階B+樹(shù)的關(guān)鍵字97
首先找到97所在的葉子節(jié)點(diǎn),刪除,然后修改該節(jié)點(diǎn)的所有父節(jié)點(diǎn)的關(guān)鍵字為91(只有刪除樹(shù)中最大的關(guān)鍵字需要做此步驟)
刪除下圖3階B+樹(shù)的關(guān)鍵字51
首先找到51所在的葉子節(jié)點(diǎn),刪除
破壞了B+樹(shù)的性質(zhì),向該節(jié)點(diǎn)的兄弟節(jié)點(diǎn)(左節(jié)點(diǎn)或右節(jié)點(diǎn))借節(jié)點(diǎn)44,并修改相應(yīng)鍵值。
刪除下圖3階B+樹(shù)的關(guān)鍵字59
首先找到59所在的葉子節(jié)點(diǎn)
破壞B+樹(shù)的性質(zhì),嘗試借節(jié)點(diǎn),無(wú)效(因?yàn)樽笮值芄?jié)點(diǎn)被借也會(huì)破壞B+樹(shù)的性質(zhì)),合并第二、第三葉節(jié)點(diǎn),并調(diào)整鍵值
刪除下圖3階B+樹(shù)的關(guān)鍵字63
首先找到63所在的葉節(jié)點(diǎn),刪除
合并第四、第五節(jié)點(diǎn)并調(diào)整鍵值
第二層的第二個(gè)節(jié)點(diǎn)不滿(mǎn)足B+樹(shù)的性質(zhì),從第二層的第一個(gè)節(jié)點(diǎn)借59并調(diào)整鍵值
B*樹(shù)
B*樹(shù)是B+樹(shù)的變體,在B+樹(shù)的非根非葉子節(jié)點(diǎn)中再增加指向兄弟節(jié)點(diǎn)的指針。B*樹(shù)定義了非葉子結(jié)點(diǎn)關(guān)鍵字個(gè)數(shù)至少為(2/3)*M,即塊的最低使用率為2/3(代替B+樹(shù)的1/2)。