深入理解InnoDB(3)—索引的存储结构
1. 索引的各種存儲(chǔ)結(jié)構(gòu)及其優(yōu)缺點(diǎn)
1.1 二叉樹
優(yōu)點(diǎn):
二叉樹是一種比順序結(jié)構(gòu)更加高效地查找目標(biāo)元素的結(jié)構(gòu),它可以從第一個(gè)父節(jié)點(diǎn)開始跟目標(biāo)元素值比較,如果相等則返回當(dāng)前節(jié)點(diǎn),如果目標(biāo)元素值小于當(dāng)前節(jié)點(diǎn),則移動(dòng)到左側(cè)子節(jié)點(diǎn)進(jìn)行比較,大于的情況則移動(dòng)到右側(cè)子節(jié)點(diǎn)進(jìn)行比較,反復(fù)進(jìn)行操作最終移動(dòng)到目標(biāo)元素節(jié)點(diǎn)位置。
缺點(diǎn):
在大部分情況下,我們?cè)O(shè)計(jì)索引時(shí)都會(huì)在表中提供一個(gè)自增整形字段作為建立索引的列,在這種場(chǎng)景下使用二叉樹的結(jié)構(gòu)會(huì)導(dǎo)致我們的索引總是添加到右側(cè),在查找記錄時(shí)跟沒加索引的情況是一樣的
1.2 紅黑樹
紅黑樹也叫平衡二叉樹,它不僅繼承了二叉樹的優(yōu)點(diǎn),而且解決了上面二叉樹遇到的自增整形索引的問題,從下面的動(dòng)態(tài)圖中可以看出紅黑樹會(huì)左旋、右旋對(duì)結(jié)構(gòu)進(jìn)行調(diào)整,始終保證左子節(jié)點(diǎn)數(shù) < 父節(jié)點(diǎn)數(shù) < 右子節(jié)點(diǎn)數(shù)的規(guī)則。
在數(shù)據(jù)量大的時(shí)候,深度也很大。從圖中可以看出每個(gè)父節(jié)點(diǎn)只能存在兩個(gè)子節(jié)點(diǎn),如果我們有很多數(shù)據(jù),那么樹的深度依然會(huì)很大,可能就會(huì)超過十幾二十層以上,對(duì)我們的磁盤尋址不利,依然會(huì)花費(fèi)很多時(shí)間查找。
1.3 Hash
對(duì)數(shù)據(jù)進(jìn)行Hash(散列)運(yùn)算,主流的Hash算法有MD5、SHA256等等,然后將哈希結(jié)果作為文件指針可以從索引文件中獲得數(shù)據(jù)的文件指針,再到數(shù)據(jù)文件中獲取到數(shù)據(jù),按照這樣的設(shè)計(jì),我們?cè)诓檎襴here Col2 = 22的記錄時(shí)只需要對(duì)22做哈希運(yùn)算得到該索引所對(duì)應(yīng)那行數(shù)據(jù)的文件指針,從而在MySQL的數(shù)據(jù)文件中定位到目標(biāo)記錄,查詢效率非常高。
無法解決范圍查詢(Range)的場(chǎng)景,比如 select count(id) from sus_user where id >10;因此Hash這種索引結(jié)構(gòu)只能針對(duì)字段名=目標(biāo)值的場(chǎng)景使用。不適合模糊查詢(like)的場(chǎng)景。
1.4 B-Tree
既然紅黑樹存在缺點(diǎn),那么我們可以在紅黑樹的基礎(chǔ)上構(gòu)思一種新的儲(chǔ)存結(jié)構(gòu)。解決的思路也很簡(jiǎn)單,既然覺得樹的深度太長(zhǎng),就只需要適當(dāng)?shù)卦黾用總€(gè)樹節(jié)點(diǎn)能存儲(chǔ)的數(shù)據(jù)個(gè)數(shù)即可,但是數(shù)據(jù)個(gè)數(shù)也必須要設(shè)定一個(gè)合理的閾值,不然一個(gè)節(jié)點(diǎn)數(shù)據(jù)個(gè)數(shù)過多會(huì)產(chǎn)生多余的消耗。
1.4.1 B-Tree的一些特點(diǎn)
- 度(Degree)-節(jié)點(diǎn)的數(shù)據(jù)存儲(chǔ)個(gè)數(shù),每個(gè)樹節(jié)點(diǎn)中數(shù)據(jù)個(gè)數(shù)大于 15/16*Degree(未驗(yàn)證) 時(shí)會(huì)自動(dòng)分裂,調(diào)整結(jié)構(gòu)
- 葉節(jié)點(diǎn)具有相同的深度,左子樹跟右子樹的深度一致
- 葉節(jié)點(diǎn)的指針為空
- 節(jié)點(diǎn)中的數(shù)據(jù)key從左到右遞增排列
BTree的結(jié)構(gòu)可以彌補(bǔ)紅黑樹的缺點(diǎn),解決數(shù)據(jù)量過大時(shí)整棵樹的深度過長(zhǎng)的問題。相同數(shù)量的數(shù)據(jù)只需要更少的層,相同深度的樹可以存儲(chǔ)更多的數(shù)據(jù),查找的效率自然會(huì)更高。
從上面得知,在查詢單條數(shù)據(jù)是非常快的。但如果范圍查的話,BTree結(jié)構(gòu)每次都要從根節(jié)點(diǎn)查詢一遍,效率會(huì)有所降低,因此在實(shí)際應(yīng)用中采用的是另一種BTree的變種B+Tree(B+樹)。
2. B+Tree—InnoDB中的索引方案
2.1 相對(duì)于BTree,B+Tree做了哪些優(yōu)化?
B+Tree存儲(chǔ)結(jié)構(gòu),只有葉子節(jié)點(diǎn)存儲(chǔ)數(shù)據(jù)
。新的B+樹結(jié)構(gòu)沒有在所有的節(jié)點(diǎn)里存儲(chǔ)記錄數(shù)據(jù),而是只在最下層的葉子節(jié)點(diǎn)存儲(chǔ),上層的所有非葉子節(jié)點(diǎn)只存放索引信息,這樣的結(jié)構(gòu)可以讓單個(gè)節(jié)點(diǎn)存放下更多索引值,增大度Degree的值,提高命中目標(biāo)記錄的幾率。
這種結(jié)構(gòu)會(huì)在上層非葉子節(jié)點(diǎn)存儲(chǔ)一部分冗余數(shù)據(jù),但是這樣的缺點(diǎn)都是可以容忍的,因?yàn)槿哂嗟亩际撬饕龜?shù)據(jù),不會(huì)對(duì)內(nèi)存造成大的負(fù)擔(dān)。這種結(jié)構(gòu)會(huì)在上層非葉子節(jié)點(diǎn)存儲(chǔ)一部分冗余數(shù)據(jù),但是這樣的缺點(diǎn)都是可以容忍的,因?yàn)槿哂嗟亩际撬饕龜?shù)據(jù),不會(huì)對(duì)內(nèi)存造成大的負(fù)擔(dān)。
InnoDB中的索引是通過目錄項(xiàng)所指向的下一層頁(yè)號(hào)來一層一層去縮小搜索范圍,從而達(dá)到高效的查詢的。通過二分法對(duì)頁(yè)內(nèi)的目錄項(xiàng)和目標(biāo)值進(jìn)行比對(duì),查出下一層所在頁(yè)號(hào),再在該頁(yè)下進(jìn)行再次搜索,直到到達(dá)葉子節(jié)點(diǎn)
2.2 索引的存儲(chǔ)結(jié)構(gòu)
索引中的目錄項(xiàng)其實(shí)長(zhǎng)得跟我們的用戶記錄差不多,只不過目錄項(xiàng)中的兩個(gè)列是主鍵和頁(yè)號(hào)而已,所以InnoDB復(fù)用了之前存儲(chǔ)用戶記錄的數(shù)據(jù)頁(yè)來存儲(chǔ)目錄項(xiàng),為了和用戶記錄做一下區(qū)分,我們把這些用來表示目錄項(xiàng)的記錄稱為目錄項(xiàng)記錄。
頁(yè)的組成結(jié)構(gòu)也是一樣的(就是我們前邊介紹過的7個(gè)部分),都會(huì)為主鍵值生成Page Directory(頁(yè)目錄),從而在按照主鍵值進(jìn)行查找時(shí)可以使用二分法來加快查詢速度。
不論是存放用戶記錄的數(shù)據(jù)頁(yè),還是存放目錄項(xiàng)記錄的數(shù)據(jù)頁(yè),我們都把它們存放到B+樹這個(gè)數(shù)據(jù)結(jié)構(gòu)中了,所以我們也稱這些數(shù)據(jù)頁(yè)為節(jié)點(diǎn)。從圖中可以看出來,我們的實(shí)際用戶記錄其實(shí)都存放在B+樹的最底層的節(jié)點(diǎn)上,這些節(jié)點(diǎn)也被稱為葉子節(jié)點(diǎn)或葉節(jié)點(diǎn),其余用來存放目錄項(xiàng)的節(jié)點(diǎn)稱為非葉子節(jié)點(diǎn)或者內(nèi)節(jié)點(diǎn),其中B+樹最上邊的那個(gè)節(jié)點(diǎn)也稱為根節(jié)點(diǎn)。
2.3 聚簇索引
我們上邊介紹的B+樹本身就是一個(gè)目錄,或者說本身就是一個(gè)索引。它有兩個(gè)特點(diǎn):
使用記錄主鍵值的大小進(jìn)行記錄和頁(yè)的排序,這包括三個(gè)方面的含義:
頁(yè)內(nèi)的記錄是按照主鍵的大小順序排成一個(gè)單向鏈表。
各個(gè)存放用戶記錄的頁(yè)也是根據(jù)頁(yè)中用戶記錄的主鍵大小順序排成一個(gè)雙向鏈表。
存放目錄項(xiàng)記錄的頁(yè)分為不同的層次,在同一層次中的頁(yè)也是根據(jù)頁(yè)中目錄項(xiàng)記錄的主鍵大小順序排成一個(gè)雙向鏈表。
B+樹的葉子節(jié)點(diǎn)存儲(chǔ)的是完整的用戶記錄。
所謂完整的用戶記錄,就是指這個(gè)記錄中存儲(chǔ)了所有列的值(包括隱藏列)。
聚簇索引的優(yōu)缺點(diǎn)
- 可以把相關(guān)數(shù)據(jù)保存在一起。
- 數(shù)據(jù)訪問更快。
- 使用覆蓋索引掃描的查詢可以直接使用頁(yè)節(jié)點(diǎn)中的主鍵值。
- 如果表在設(shè)計(jì)和查詢的時(shí)候能充分利用以上特點(diǎn),將會(huì)極大提高性能。當(dāng)然,聚簇索引也有它的缺點(diǎn):
- 聚簇索引最大限度提高了I/O密集型應(yīng)用的性能,但如果所有的數(shù)據(jù)都存放在內(nèi)存中,聚簇索引就沒有優(yōu)勢(shì)了。
- 插入速度嚴(yán)重依賴插入順序。這也是為什么InnoDB一般都會(huì)設(shè)置一個(gè)自增的int列作為主鍵。
- 更新聚簇索引的代價(jià)很高,因?yàn)闀?huì)強(qiáng)制InnoDB將每個(gè)被更新的行移到新的位置。
- 如果不安順序插入新數(shù)據(jù)時(shí),可能會(huì)導(dǎo)致"頁(yè)分裂"。
- 二級(jí)索引可能會(huì)比想象的更大。因?yàn)樵诙?jí)索引的頁(yè)子節(jié)點(diǎn)中包含了引用行的主鍵列。
- 二級(jí)索引訪問可能會(huì)需要進(jìn)行回表查詢。
2.4 二級(jí)索引
聚簇索引是根據(jù)主鍵比較大小的,而二級(jí)索引是通過用戶建立索引的字段來比較大小的。
并且B+樹的葉子節(jié)點(diǎn)存儲(chǔ)的并不是完整的用戶記錄,而只是c2列+主鍵這兩個(gè)列的值。目錄項(xiàng)記錄中不再是主鍵+頁(yè)號(hào)的搭配,而變成了c2列+頁(yè)號(hào)+主鍵的搭配。
并且如果需要查詢非索引字段的信息,需要拿著主鍵id回去聚簇索引中查找
2.5 聯(lián)合索引
在二級(jí)索引的基礎(chǔ),采用多個(gè)字段進(jìn)行比較,先根據(jù)最左邊的索引排一次序,如果存在相同的值,則根據(jù)次左邊的索引字段進(jìn)行比較,如此類推。
3. InnoDB的B+樹索引的注意事項(xiàng)
B+樹的形成過程是這樣的
-
每當(dāng)為某個(gè)表創(chuàng)建一個(gè)B+樹索引(聚簇索引不是人為創(chuàng)建的,默認(rèn)就有)的時(shí)候,都會(huì)為這個(gè)索引創(chuàng)建一個(gè)根節(jié)點(diǎn)頁(yè)面。最開始表中沒有數(shù)據(jù)的時(shí)候,每個(gè)B+樹索引對(duì)應(yīng)的根節(jié)點(diǎn)中既沒有用戶記錄,也沒有目錄項(xiàng)記錄。
-
隨后向表中插入用戶記錄時(shí),先把用戶記錄存儲(chǔ)到這個(gè)根節(jié)點(diǎn)中。
-
當(dāng)根節(jié)點(diǎn)中的可用空間用完時(shí)繼續(xù)插入記錄,此時(shí)會(huì)將根節(jié)點(diǎn)中的所有記錄復(fù)制到一個(gè)新分配的頁(yè),比如頁(yè)a中,然后對(duì)這個(gè)新頁(yè)進(jìn)行頁(yè)分裂的操作,得到另一個(gè)新頁(yè),比如頁(yè)b。這時(shí)新插入的記錄根據(jù)鍵值(也就是聚簇索引中的主鍵值,二級(jí)索引中對(duì)應(yīng)的索引列的值)的大小就會(huì)被分配到頁(yè)a或者頁(yè)b中,而根節(jié)點(diǎn)便升級(jí)為存儲(chǔ)目錄項(xiàng)記錄的頁(yè)。
這個(gè)過程需要大家特別注意的是:一個(gè)B+樹索引的根節(jié)點(diǎn)自誕生之日起,便不會(huì)再移動(dòng)。這樣只要我們對(duì)某個(gè)表建立一個(gè)索引,那么它的根節(jié)點(diǎn)的頁(yè)號(hào)便會(huì)被記錄到某個(gè)地方,然后凡是InnoDB存儲(chǔ)引擎需要用到這個(gè)索引的時(shí)候,都會(huì)從那個(gè)固定的地方取出根節(jié)點(diǎn)的頁(yè)號(hào),從而來訪問這個(gè)索引。
總結(jié)
以上是生活随笔為你收集整理的深入理解InnoDB(3)—索引的存储结构的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 梦到蟒蛇吃人什么意思
- 下一篇: 深入理解InnoDB(4)—索引使用