一侈玄、索引的本質(zhì)
MySQL官方對(duì)索引的定義為:索引(Index)是幫助MySQL高效獲取數(shù)據(jù)的數(shù)據(jù)結(jié)構(gòu)。提取句子主干举农,就可以得到索引的本質(zhì):索引是數(shù)據(jù)結(jié)構(gòu)。
我們知道腌且,數(shù)據(jù)庫(kù)查詢是數(shù)據(jù)庫(kù)的最主要功能之一梗肝。我們都希望查詢數(shù)據(jù)的速度能盡可能的快,因此數(shù)據(jù)庫(kù)系統(tǒng)的設(shè)計(jì)者會(huì)從查詢算法的角度進(jìn)行優(yōu)化铺董。最基本的查詢算法當(dāng)然是順序查找(linear search)巫击,這種復(fù)雜度為O(n)的算法在數(shù)據(jù)量很大時(shí)顯然是糟糕的,好在計(jì)算機(jī)科學(xué)的發(fā)展提供了很多更優(yōu)秀的查找算法精续,例如二分查找(binary search)坝锰、二叉樹(shù)查找(binary tree search)等。如果稍微分析一下會(huì)發(fā)現(xiàn)重付,每種查找算法都只能應(yīng)用于特定的數(shù)據(jù)結(jié)構(gòu)之上什黑,例如二分查找要求被檢索數(shù)據(jù)有序,而二叉樹(shù)查找只能應(yīng)用于二叉查找樹(shù)上堪夭,但是數(shù)據(jù)本身的組織結(jié)構(gòu)不可能完全滿足各種數(shù)據(jù)結(jié)構(gòu)(例如,理論上不可能同時(shí)將兩列都按順序進(jìn)行組織)拣凹,所以森爽,在數(shù)據(jù)之外,數(shù)據(jù)庫(kù)系統(tǒng)還維護(hù)著滿足特定查找算法的數(shù)據(jù)結(jié)構(gòu)嚣镜,這些數(shù)據(jù)結(jié)構(gòu)以某種方式引用(指向)數(shù)據(jù)爬迟,這樣就可以在這些數(shù)據(jù)結(jié)構(gòu)上實(shí)現(xiàn)高級(jí)查找算法。這種數(shù)據(jù)結(jié)構(gòu)菊匿,就是索引付呕。
看一個(gè)例子:
上圖展示了一種可能的索引方式。左邊是數(shù)據(jù)表跌捆,一共有兩列七條記錄徽职,最左邊的是數(shù)據(jù)記錄的物理地址(注意邏輯上相鄰的記錄在磁盤(pán)上也并不是一定物理相鄰的)。為了加快Col2的查找佩厚,可以維護(hù)一個(gè)右邊所示的二叉查找樹(shù)姆钉,每個(gè)節(jié)點(diǎn)分別包含索引鍵值和一個(gè)指向?qū)?yīng)數(shù)據(jù)記錄物理地址的指針,這樣就可以運(yùn)用二叉查找在O(logn2)O(log2n)的復(fù)雜度內(nèi)獲取到相應(yīng)數(shù)據(jù)抄瓦。
雖然這是一個(gè)貨真價(jià)實(shí)的索引潮瓶,但是實(shí)際的數(shù)據(jù)庫(kù)系統(tǒng)幾乎沒(méi)有使用二叉查找樹(shù)或其進(jìn)化品種紅黑樹(shù)(red-black tree)實(shí)現(xiàn)的,原因會(huì)在下文介紹钙姊。
二毯辅、二叉排序樹(shù)
在介紹B樹(shù)之前,先來(lái)看另一棵神奇的樹(shù)——二叉排序樹(shù)(Binary Sort Tree)煞额,首先它是一棵樹(shù)思恐,“二叉”這個(gè)描述已經(jīng)很明顯了沾谜,就是樹(shù)上的一根樹(shù)枝開(kāi)兩個(gè)叉,于是遞歸下來(lái)就是二叉樹(shù)了(下圖所示)壁袄,而這棵樹(shù)上的節(jié)點(diǎn)是已經(jīng)排好序的类早,具體的排序規(guī)則如下:
- 若左子樹(shù)不空,則左子樹(shù)上所有節(jié)點(diǎn)的值均小于它的根節(jié)點(diǎn)的值
- 若右子樹(shù)不空嗜逻,則右字?jǐn)?shù)上所有節(jié)點(diǎn)的值均大于它的根節(jié)點(diǎn)的值
- 它的左涩僻、右子樹(shù)也分別為二叉排序數(shù)(遞歸定義)
從圖中可以看出,二叉排序樹(shù)組織數(shù)據(jù)時(shí)栈顷,用于查找是比較方便的逆日,因?yàn)槊看谓?jīng)過(guò)一次節(jié)點(diǎn)時(shí),最多可以減少一半的可能萄凤,不過(guò)極端情況會(huì)出現(xiàn)所有節(jié)點(diǎn)都位于同一側(cè)室抽,直觀上看就是一條直線,那么這種查詢的效率就比較低了靡努,因此需要對(duì)二叉樹(shù)左右子樹(shù)的高度進(jìn)行平衡化處理坪圾,于是就有了平衡二叉樹(shù)(Balenced Binary Tree)。
所謂“平衡”惑朦,說(shuō)的是這棵樹(shù)的各個(gè)分支的高度是均勻的兽泄,它的左子樹(shù)和右子樹(shù)的高度之差絕對(duì)值小于1,這樣就不會(huì)出現(xiàn)一條支路特別長(zhǎng)的情況漾月。于是病梢,在這樣的平衡樹(shù)中進(jìn)行查找時(shí),總共比較節(jié)點(diǎn)的次數(shù)不超過(guò)樹(shù)的高度梁肿,這就確保了查詢的效率(時(shí)間復(fù)雜度為O(logn))
三蜓陌、B樹(shù)
還是直接看圖比較清楚,圖中所示吩蔑,B樹(shù)事實(shí)上是一種平衡的多叉查找樹(shù)钮热,也就是說(shuō)最多可以開(kāi)m個(gè)叉(m>=2),我們稱之為m階b樹(shù)烛芬,為了體現(xiàn)本博客的良心之處霉旗,不同于其他地方都能看到2階B樹(shù),這里特意畫(huà)了一棵5階B樹(shù) 蛀骇。
總的來(lái)說(shuō)厌秒,m階B樹(shù)滿足以下條件:
- 每個(gè)節(jié)點(diǎn)至多可以擁有m棵子樹(shù)。
- 根節(jié)點(diǎn)擅憔,只有至少有2個(gè)節(jié)點(diǎn)(要么極端情況鸵闪,就是一棵樹(shù)就一個(gè)根節(jié)點(diǎn),單細(xì)胞生物暑诸,即是根蚌讼,也是葉辟灰,也是樹(shù))。
- 非根非葉的節(jié)點(diǎn)至少有的Ceil(m/2)個(gè)子樹(shù)(Ceil表示向上取整篡石,圖中5階B樹(shù)芥喇,每個(gè)節(jié)點(diǎn)至少有3個(gè)子樹(shù),也就是至少有3個(gè)叉)凰萨。
- 非葉節(jié)點(diǎn)中的信息包括[n,A0,K1,A1,K2,A2,…,Kn,An]继控,,其中n表示該節(jié)點(diǎn)中保存的關(guān)鍵字個(gè)數(shù)胖眷,K為關(guān)鍵字且Ki<Ki+1武通,A為指向子樹(shù)根節(jié)點(diǎn)的指針。
- 從根到葉子的每一條路徑都有相同的長(zhǎng)度珊搀,也就是說(shuō)冶忱,葉子節(jié)在相同的層,并且這些節(jié)點(diǎn)不帶信息境析,實(shí)際上這些節(jié)點(diǎn)就表示找不到指定的值囚枪,也就是指向這些節(jié)點(diǎn)的指針為空。
- B樹(shù)的查詢過(guò)程和二叉排序樹(shù)比較類似劳淆,從根節(jié)點(diǎn)依次比較每個(gè)結(jié)點(diǎn)眶拉,因?yàn)槊總€(gè)節(jié)點(diǎn)中的關(guān)鍵字和左右子樹(shù)都是有序的,所以只要比較節(jié)點(diǎn)中的關(guān)鍵字憔儿,或者沿著指針就能很快地找到指定的關(guān)鍵字,如果查找失敗放可,則會(huì)返回葉子節(jié)點(diǎn)谒臼,即空指針。
例如查詢圖中字母表中的K:
- 從根節(jié)點(diǎn)P開(kāi)始耀里,K的位置在P之前蜈缤,進(jìn)入左側(cè)指針。
- 左子樹(shù)中冯挎,依次比較C底哥、F、J房官、M趾徽,發(fā)現(xiàn)K在J和M之間。
- 沿著J和M之間的指針翰守,繼續(xù)訪問(wèn)子樹(shù)孵奶,并依次進(jìn)行比較,發(fā)現(xiàn)第一個(gè)關(guān)鍵字K即為指定查找的值蜡峰。
B樹(shù)搜索的簡(jiǎn)單偽算法如下:
BTree_Search(node, key) {
if(node == null) return null;
foreach(node.key)
{
if(node.key[i] == key) return node.data[i];
if(node.key[i] > key) return BTree_Search(point[i]->node);
}
return BTree_Search(point[i+1]->node);
}
data = BTree_Search(root, my_key);
B樹(shù)的特點(diǎn)可以總結(jié)為如下:
- 關(guān)鍵字集合分布在整顆樹(shù)中了袁。
- 任何一個(gè)關(guān)鍵字出現(xiàn)且只出現(xiàn)在一個(gè)節(jié)點(diǎn)中朗恳。
- 搜索有可能在非葉子節(jié)點(diǎn)結(jié)束。
- 其搜索性能等價(jià)于在關(guān)鍵字集合內(nèi)做一次二分查找载绿。
- B樹(shù)在插入刪除新的數(shù)據(jù)記錄會(huì)破壞B-Tree的性質(zhì)粥诫,因?yàn)樵诓迦雱h除時(shí),需要對(duì)樹(shù)進(jìn)行一個(gè)分裂崭庸、合并怀浆、轉(zhuǎn)移等操作以保持B-Tree性質(zhì)。
四冀自、Plus版 — B+樹(shù)
作為B樹(shù)的加強(qiáng)版揉稚,B+樹(shù)與B樹(shù)的差異在于
有n棵子樹(shù)的節(jié)點(diǎn)含有n個(gè)關(guān)鍵字(也有認(rèn)為是n-1個(gè)關(guān)鍵字)。
所有的關(guān)鍵字全部存儲(chǔ)在葉子節(jié)點(diǎn)上熬粗,且葉子節(jié)點(diǎn)本身根據(jù)關(guān)鍵字自小而大順序連接搀玖。
非葉子節(jié)點(diǎn)可以看成索引部分,節(jié)點(diǎn)中僅含有其子樹(shù)(根節(jié)點(diǎn))中的最大(或最凶つ拧)關(guān)鍵字灌诅。
B+樹(shù)的查找過(guò)程,與B樹(shù)類似含末,只不過(guò)查找時(shí)猜拾,如果在非葉子節(jié)點(diǎn)上的關(guān)鍵字等于給定值,并不終止佣盒,而是繼續(xù)沿著指針直到葉子節(jié)點(diǎn)位置挎袜。因此在B+樹(shù),不管查找成功與否肥惭,每次查找都是走了一條從根到葉子節(jié)點(diǎn)的路徑盯仪。
B+樹(shù)的特性如下:
- 所有關(guān)鍵字都存儲(chǔ)在葉子節(jié)上,且鏈表中的關(guān)鍵字恰好是有序的蜜葱。
- 不可能非葉子節(jié)點(diǎn)命中返回全景。
- 非葉子節(jié)點(diǎn)相當(dāng)于葉子節(jié)點(diǎn)的索引,葉子節(jié)點(diǎn)相當(dāng)于是存儲(chǔ)(關(guān)鍵字)數(shù)據(jù)的數(shù)據(jù)層牵囤。
- 更適合文件索引系統(tǒng)爸黄。
五、帶有順序訪問(wèn)指針的B+Tree
一般在數(shù)據(jù)庫(kù)系統(tǒng)或文件系統(tǒng)中使用的B+Tree結(jié)構(gòu)都在經(jīng)典B+Tree的基礎(chǔ)上進(jìn)行了優(yōu)化揭鳞,增加了順序訪問(wèn)指針炕贵。
如上圖所示,在B+Tree的每個(gè)葉子節(jié)點(diǎn)增加一個(gè)指向相鄰葉子節(jié)點(diǎn)的指針野崇,就形成了帶有順序訪問(wèn)指針的B+Tree鲁驶。做這個(gè)優(yōu)化的目的是為了提高區(qū)間訪問(wèn)的性能,例如圖4中如果要查詢key為從18到49的所有數(shù)據(jù)記錄舞骆,當(dāng)找到18后钥弯,只需順著節(jié)點(diǎn)和指針順序遍歷就可以一次性訪問(wèn)到所有數(shù)據(jù)節(jié)點(diǎn)径荔,極大提到了區(qū)間查詢效率。
六脆霎、MySQL為什么使用B樹(shù)(B+樹(shù))
紅黑樹(shù)等數(shù)據(jù)結(jié)構(gòu)也可以用來(lái)實(shí)現(xiàn)索引总处,但是文件系統(tǒng)以及數(shù)據(jù)庫(kù)系統(tǒng)普遍采用B樹(shù)或者B+樹(shù),這一節(jié)將結(jié)合計(jì)算機(jī)組成原理相關(guān)知識(shí)討論B-/+Tree作為索引的理論基礎(chǔ)睛蛛。
一般來(lái)說(shuō)鹦马,索引本身也很大,不可能全部存儲(chǔ)在內(nèi)存中忆肾,因此索引往往以索引文件的形式存儲(chǔ)在磁盤(pán)上荸频。這樣的話,索引查找過(guò)程中就要產(chǎn)生磁盤(pán)I/O消耗客冈,相對(duì)于內(nèi)存存取旭从,I/O存取的消耗要高幾個(gè)數(shù)量級(jí),所以評(píng)價(jià)一個(gè)數(shù)據(jù)結(jié)構(gòu)作為索引的優(yōu)劣最重要的指標(biāo)就是在查找過(guò)程中磁盤(pán)I/O操作次數(shù)的漸進(jìn)復(fù)雜度场仲。換句話說(shuō)和悦,索引的結(jié)構(gòu)組織要盡量減少查找過(guò)程中磁盤(pán)I/O的存取次數(shù)。下面先介紹內(nèi)存和磁盤(pán)存取原理渠缕,然后再結(jié)合這些原理分析B-/+Tree作為索引的效率鸽素。
1. 主存存取原理
目前計(jì)算機(jī)使用的主存基本都是隨機(jī)讀寫(xiě)存儲(chǔ)器(RAM),現(xiàn)代RAM的結(jié)構(gòu)和存取原理比較復(fù)雜亦鳞,這里本文拋卻具體差別馍忽,抽象出一個(gè)十分簡(jiǎn)單的存取模型來(lái)說(shuō)明RAM的工作原理。
從抽象角度看燕差,主存是一系列的存儲(chǔ)單元組成的矩陣遭笋,每個(gè)存儲(chǔ)單元存儲(chǔ)固定大小的數(shù)據(jù)。每個(gè)存儲(chǔ)單元有唯一的地址谁不,現(xiàn)代主存的編址規(guī)則比較復(fù)雜,這里將其簡(jiǎn)化成一個(gè)二維地址:通過(guò)一個(gè)行地址和一個(gè)列地址可以唯一定位到一個(gè)存儲(chǔ)單元徽诲。上圖展示了一個(gè)4 x 4的主存模型刹帕。
主存的存取過(guò)程如下:
當(dāng)系統(tǒng)需要讀取主存時(shí),則將地址信號(hào)放到地址總線上傳給主存谎替,主存讀到地址信號(hào)后偷溺,解析信號(hào)并定位到指定存儲(chǔ)單元,然后將此存儲(chǔ)單元數(shù)據(jù)放到數(shù)據(jù)總線上钱贯,供其它部件讀取挫掏。
寫(xiě)主存的過(guò)程類似,系統(tǒng)將要寫(xiě)入單元地址和數(shù)據(jù)分別放在地址總線和數(shù)據(jù)總線上秩命,主存讀取兩個(gè)總線的內(nèi)容尉共,做相應(yīng)的寫(xiě)操作褒傅。
這里可以看出,主存存取的時(shí)間僅與存取次數(shù)呈線性關(guān)系袄友,因?yàn)椴淮嬖跈C(jī)械操作殿托,兩次存取的數(shù)據(jù)的“距離”不會(huì)對(duì)時(shí)間有任何影響,例如剧蚣,先取A0再取A1和先取A0再取D3的時(shí)間消耗是一樣的支竹。
2. 磁盤(pán)存取原理
上文說(shuō)過(guò),索引一般以文件形式存儲(chǔ)在磁盤(pán)上鸠按,索引檢索需要磁盤(pán)I/O操作礼搁。與主存不同,磁盤(pán)I/O存在機(jī)械運(yùn)動(dòng)耗費(fèi)目尖,因此磁盤(pán)I/O的時(shí)間消耗是巨大的馒吴。
下圖是磁盤(pán)的整體結(jié)構(gòu)示意圖:
一個(gè)磁盤(pán)由大小相同且同軸的圓形盤(pán)片組成,磁盤(pán)可以轉(zhuǎn)動(dòng)(各個(gè)磁盤(pán)必須同步轉(zhuǎn)動(dòng))卑雁。在磁盤(pán)的一側(cè)有磁頭支架募书,磁頭支架固定了一組磁頭,每個(gè)磁頭負(fù)責(zé)存取一個(gè)磁盤(pán)的內(nèi)容测蹲。磁頭不能轉(zhuǎn)動(dòng)莹捡,但是可以沿磁盤(pán)半徑方向運(yùn)動(dòng)(實(shí)際是斜切向運(yùn)動(dòng)),每個(gè)磁頭同一時(shí)刻也必須是同軸的扣甲,即從正上方向下看篮赢,所有磁頭任何時(shí)候都是重疊的(不過(guò)目前已經(jīng)有多磁頭獨(dú)立技術(shù),可不受此限制)琉挖。
下圖是磁盤(pán)結(jié)構(gòu)的示意圖:
盤(pán)片被劃分成一系列同心環(huán)启泣,圓心是盤(pán)片中心,每個(gè)同心環(huán)叫做一個(gè)磁道示辈,所有半徑相同的磁道組成一個(gè)柱面寥茫。磁道被沿半徑線劃分成一個(gè)個(gè)小的段,每個(gè)段叫做一個(gè)扇區(qū)矾麻,每個(gè)扇區(qū)是磁盤(pán)的最小存儲(chǔ)單元纱耻。為了簡(jiǎn)單起見(jiàn),我們下面假設(shè)磁盤(pán)只有一個(gè)盤(pán)片和一個(gè)磁頭险耀。
當(dāng)需要從磁盤(pán)讀取數(shù)據(jù)時(shí)弄喘,系統(tǒng)會(huì)將數(shù)據(jù)邏輯地址傳給磁盤(pán),磁盤(pán)的控制電路按照尋址邏輯將邏輯地址翻譯成物理地址甩牺,即確定要讀的數(shù)據(jù)在哪個(gè)磁道蘑志,哪個(gè)扇區(qū)。為了讀取這個(gè)扇區(qū)的數(shù)據(jù),需要將磁頭放到這個(gè)扇區(qū)上方急但,為了實(shí)現(xiàn)這一點(diǎn)澎媒,磁頭需要移動(dòng)對(duì)準(zhǔn)相應(yīng)磁道,這個(gè)過(guò)程叫做尋道羊始,所耗費(fèi)時(shí)間叫做尋道時(shí)間旱幼,然后磁盤(pán)旋轉(zhuǎn)將目標(biāo)扇區(qū)旋轉(zhuǎn)到磁頭下,這個(gè)過(guò)程耗費(fèi)的時(shí)間叫做旋轉(zhuǎn)時(shí)間突委。
3. 局部性原理與磁盤(pán)預(yù)讀
由于存儲(chǔ)介質(zhì)的特性柏卤,磁盤(pán)本身存取就比主存慢很多,再加上機(jī)械運(yùn)動(dòng)耗費(fèi)匀油,磁盤(pán)的存取速度往往是主存的幾百分分之一缘缚,因此為了提高效率,要盡量減少磁盤(pán)I/O敌蚜。為了達(dá)到這個(gè)目的桥滨,磁盤(pán)往往不是嚴(yán)格按需讀取,而是每次都會(huì)預(yù)讀弛车,即使只需要一個(gè)字節(jié)齐媒,磁盤(pán)也會(huì)從這個(gè)位置開(kāi)始,順序向后讀取一定長(zhǎng)度的數(shù)據(jù)放入內(nèi)存纷跛。這樣做的理論依據(jù)是計(jì)算機(jī)科學(xué)中著名的局部性原理:
當(dāng)一個(gè)數(shù)據(jù)被用到時(shí)喻括,其附近的數(shù)據(jù)也通常會(huì)馬上被使用。
所以贫奠,程序運(yùn)行期間所需要的數(shù)據(jù)通常應(yīng)當(dāng)比較集中唬血。
由于磁盤(pán)順序讀取的效率很高(不需要尋道時(shí)間,只需很少的旋轉(zhuǎn)時(shí)間)唤崭,因此對(duì)于具有局部性的程序來(lái)說(shuō)拷恨,預(yù)讀可以提高I/O效率。
預(yù)讀的長(zhǎng)度一般為頁(yè)(page)的整倍數(shù)谢肾。頁(yè)是計(jì)算機(jī)管理存儲(chǔ)器的邏輯塊腕侄,硬件及操作系統(tǒng)往往將主存和磁盤(pán)存儲(chǔ)區(qū)分割為連續(xù)的大小相等的塊,每個(gè)存儲(chǔ)塊稱為一頁(yè)(在許多操作系統(tǒng)中芦疏,頁(yè)得大小通常為4k)冕杠,主存和磁盤(pán)以頁(yè)為單位交換數(shù)據(jù)。當(dāng)程序要讀取的數(shù)據(jù)不在主存中時(shí)眯分,會(huì)觸發(fā)一個(gè)缺頁(yè)異常拌汇,此時(shí)系統(tǒng)會(huì)向磁盤(pán)發(fā)出讀盤(pán)信號(hào)柒桑,磁盤(pán)會(huì)找到數(shù)據(jù)的起始位置并向后連續(xù)讀取一頁(yè)或幾頁(yè)載入內(nèi)存中弊决,然后異常返回,程序繼續(xù)運(yùn)行。
4. B-/+Tree索引的性能分析
到這里終于可以分析B-/+Tree索引的性能了飘诗。
上文說(shuō)過(guò)一般使用磁盤(pán)I/O次數(shù)評(píng)價(jià)索引結(jié)構(gòu)的優(yōu)劣与倡。先從B-Tree分析,根據(jù)B-Tree的定義昆稿,可知檢索一次最多需要訪問(wèn)h個(gè)節(jié)點(diǎn)纺座。數(shù)據(jù)庫(kù)系統(tǒng)的設(shè)計(jì)者巧妙利用了磁盤(pán)預(yù)讀原理,將一個(gè)節(jié)點(diǎn)的大小設(shè)為等于一個(gè)頁(yè)溉潭,這樣每個(gè)節(jié)點(diǎn)只需要一次I/O就可以完全載入净响。為了達(dá)到這個(gè)目的,在實(shí)際實(shí)現(xiàn)B-Tree還需要使用如下技巧:
每次新建節(jié)點(diǎn)時(shí)喳瓣,直接申請(qǐng)一個(gè)頁(yè)的空間馋贤,這樣就保證一個(gè)節(jié)點(diǎn)物理上也存儲(chǔ)在一個(gè)頁(yè)里,加之計(jì)算機(jī)存儲(chǔ)分配都是按頁(yè)對(duì)齊的畏陕,就實(shí)現(xiàn)了一個(gè)node只需一次I/O配乓。
B-Tree中一次檢索最多需要h-1次I/O(根節(jié)點(diǎn)常駐內(nèi)存),漸進(jìn)復(fù)雜度為O(h)=O(logdN)O(h)=O(logdN)惠毁。一般實(shí)際應(yīng)用中犹芹,出度d是非常大的數(shù)字,通常超過(guò)100鞠绰,因此h非常醒 (通常不超過(guò)3)。(h表示樹(shù)的高度 & 出度d表示的是樹(shù)的度洞豁,即樹(shù)中各個(gè)節(jié)點(diǎn)的度的最大值)
綜上所述盐固,用B-Tree作為索引結(jié)構(gòu)效率是非常高的。
而紅黑樹(shù)這種結(jié)構(gòu)丈挟,h明顯要深的多刁卜。由于邏輯上很近的節(jié)點(diǎn)(父子)物理上可能很遠(yuǎn),無(wú)法利用局部性曙咽,所以紅黑樹(shù)的I/O漸進(jìn)復(fù)雜度也為O(h)蛔趴,效率明顯比B-Tree差很多。
上文還說(shuō)過(guò)例朱,B+Tree更適合外存索引孝情,原因和內(nèi)節(jié)點(diǎn)出度d有關(guān)。從上面分析可以看到洒嗤,d越大索引的性能越好箫荡,而出度的上限取決于節(jié)點(diǎn)內(nèi)key和data的大小:
dmax=floor(pagesize/(keysize+datasize+pointsize))
floor表示向下取整渔隶。由于B+Tree內(nèi)節(jié)點(diǎn)去掉了data域羔挡,因此可以擁有更大的出度洁奈,擁有更好的性能。
七绞灼、MySQL索引實(shí)現(xiàn)
在MySQL中利术,索引屬于存儲(chǔ)引擎級(jí)別的概念,不同存儲(chǔ)引擎對(duì)索引的實(shí)現(xiàn)方式是不同的低矮,本文主要討論MyISAM和InnoDB兩個(gè)存儲(chǔ)引擎的索引實(shí)現(xiàn)方式印叁。
1. MyISAM索引實(shí)現(xiàn)
MyISAM引擎使用B+Tree作為索引結(jié)構(gòu),葉節(jié)點(diǎn)的data域存放的是數(shù)據(jù)記錄的地址军掂。下圖是MyISAM索引的原理圖:
這里設(shè)表一共有三列轮蜕,假設(shè)我們以Col1為主鍵,則上圖是一個(gè)MyISAM表的主索引(Primary key)示意蝗锥〕λ洌可以看出MyISAM的索引文件僅僅保存數(shù)據(jù)記錄的地址。在MyISAM中玛追,主索引和輔助索引(Secondary key)在結(jié)構(gòu)上沒(méi)有任何區(qū)別税课,只是主索引要求key是唯一的,而輔助索引的key可以重復(fù)痊剖。如果我們?cè)贑ol2上建立一個(gè)輔助索引韩玩,則此索引的結(jié)構(gòu)如下圖所示:
同樣也是一棵B+樹(shù),data域保存數(shù)據(jù)記錄的地址陆馁。因此找颓,MyISAM中索引檢索的算法為首先按照B+Tree搜索算法搜索索引,如果指定的Key存在叮贩,則取出其data域的值击狮,然后以data域的 值為地址,讀取相應(yīng)數(shù)據(jù)記錄益老。
MyISAM的索引方式也叫做“非聚集”的彪蓬,之所以這么稱呼是為了與InnoDB的聚集索引區(qū)分。
2. InnoDB索引實(shí)現(xiàn)
雖然InnoDB也使用B+Tree作為索引結(jié)構(gòu)捺萌,但具體實(shí)現(xiàn)方式卻與MyISAM截然不同档冬。
第一個(gè)重大區(qū)別是InnoDB的數(shù)據(jù)文件本身就是索引文件。從上文知道桃纯,MyISAM索引文件和數(shù)據(jù)文件是分離的酷誓,索引文件僅保存數(shù)據(jù)記錄的地址。而在InnoDB中态坦,表數(shù)據(jù)文件本身就是按B+Tree組織的一個(gè)索引結(jié)構(gòu)盐数,這棵樹(shù)的葉節(jié)點(diǎn)data域保存了完整的數(shù)據(jù)記錄。這個(gè)索引的key是數(shù)據(jù)表的主鍵伞梯,因此InnoDB表數(shù)據(jù)文件本身就是主索引玫氢。
上圖是InnoDB主索引(同時(shí)也是數(shù)據(jù)文件)的示意圖着茸,可以看到葉節(jié)點(diǎn)包含了完整的數(shù)據(jù)記錄。這種索引叫做聚集索引琐旁。因?yàn)镮nnoDB的數(shù)據(jù)文件本身要按主鍵聚集,所以InnoDB要求表必須有主鍵(MyISAM可以沒(méi)有)猜绣,如果沒(méi)有顯式指定灰殴,則MySQL系統(tǒng)會(huì)自動(dòng)選擇一個(gè)可以唯一標(biāo)識(shí)數(shù)據(jù)記錄的列作為主鍵,如果不存在這種列掰邢,則MySQL自動(dòng)為InnoDB表生成一個(gè)隱含字段作為主鍵牺陶,這個(gè)字段長(zhǎng)度為6個(gè)字節(jié),類型為長(zhǎng)整型辣之。
第二個(gè)與MyISAM索引的不同是InnoDB的輔助索引data域存儲(chǔ)相應(yīng)記錄主鍵的值而不是地址掰伸。換句話說(shuō),InnoDB的所有輔助索引都引用主鍵作為data域怀估。例如狮鸭,上圖為定義在Col3上的一個(gè)輔助索引:
這里以英文字符的ASCII碼作為比較準(zhǔn)則。聚集索引這種實(shí)現(xiàn)方式使得按主鍵的搜索十分高效多搀,但是輔助索引搜索需要檢索兩遍索引:首先檢索輔助索引獲得主鍵歧蕉,然后用主鍵到主索引中檢索獲得記錄。
了解不同存儲(chǔ)引擎的索引實(shí)現(xiàn)方式對(duì)于正確使用和優(yōu)化索引都非常有幫助康铭,例如知道了InnoDB的索引實(shí)現(xiàn)后惯退,就很容易明白為什么不建議使用過(guò)長(zhǎng)的字段作為主鍵,因?yàn)樗休o助索引都引用主索引从藤,過(guò)長(zhǎng)的主索引會(huì)令輔助索引變得過(guò)大催跪。再例如,用非單調(diào)的字段作為主鍵在InnoDB中不是個(gè)好主意夷野,因?yàn)镮nnoDB數(shù)據(jù)文件本身是一棵B+Tree懊蒸,非單調(diào)的主鍵會(huì)造成在插入新記錄時(shí)數(shù)據(jù)文件為了維持B+Tree的特性而頻繁的分裂調(diào)整,十分低效悯搔,而使用自增字段作為主鍵則是一個(gè)很好的選擇榛鼎。
Java_蘇先生:專注于Java開(kāi)發(fā)技術(shù)的研究與知識(shí)分享!
————END————
- 點(diǎn)贊(感謝)
- ...
- 轉(zhuǎn)發(fā)(感謝)
- ...
- 關(guān)注(感謝)
- ...