目錄
1. 引言
2. 為什么要使用索引?
3. 索引的概述
4. 索引的優(yōu)點(diǎn)是什么?
4.1 降低數(shù)據(jù)庫(kù)的IO成本,提高數(shù)據(jù)查找效率
4.2 保證數(shù)據(jù)庫(kù)每一行數(shù)據(jù)的唯一性
4.3 加速表與表之間的連接
4.4 減少查詢中分組與排序的執(zhí)行時(shí)間
?5. 索引的缺點(diǎn)是什么?
5.1 創(chuàng)建索引和維護(hù)索引非常耗費(fèi)時(shí)間
5.2 索引也是占用磁盤(pán)空間的
5.3 索引會(huì)降低表的更新速度
6. B+ 樹(shù)到底是什么樣的?
7. 數(shù)據(jù)庫(kù)底層存儲(chǔ)數(shù)據(jù)的實(shí)質(zhì)
8. 索引的實(shí)現(xiàn)原理
9. 為什么B+樹(shù)的高度不建議超過(guò)四層?
10. 什么是聚簇索引?
10.1 聚簇索引的特點(diǎn)
10.2 聚簇索引的優(yōu)點(diǎn)?
10.3 聚簇索引的缺點(diǎn)?
10.4 聚簇索引的補(bǔ)充說(shuō)明
11. 什么是非聚簇索引?
12. 什么是回表?
13. 為什么非聚簇索引的葉子節(jié)點(diǎn)不存儲(chǔ)一份完整的數(shù)據(jù)方便查找呢?
14. 聚簇索引與非聚簇索引的區(qū)別?
15. 什么是聯(lián)合索引?
1. 引言
我們知道,只要談起數(shù)據(jù)庫(kù),索引重中之重,不管你是面試開(kāi)發(fā)程序員,架構(gòu)師,還是校園招聘,你會(huì)發(fā)現(xiàn)幾乎都會(huì)問(wèn)到索引。那么它到底是個(gè)什么東西?竟能如此重要。它到底有什么好處呢?為什么面試的時(shí)候總喜歡問(wèn)索引呢?它的底層實(shí)現(xiàn)是什么樣的呢?今天我們就一同來(lái)揭曉這些問(wèn)題的答案。
想要更加透徹深入的學(xué)會(huì)索引,建議你對(duì)數(shù)據(jù)結(jié)構(gòu)有很好的了解,特別是樹(shù)和鏈表,如果你對(duì)樹(shù)鏈表這種數(shù)據(jù)結(jié)構(gòu)很清晰,那么學(xué)會(huì)索引非常輕松。
2. 為什么要使用索引?
想要理解索引,我們首先要知道為什么要使用索引。
我來(lái)說(shuō)一個(gè)場(chǎng)景,假設(shè)現(xiàn)在我的一張用戶表中有十萬(wàn)條用戶數(shù)據(jù),用戶 id 唯一,我想要查詢id 位80000的用戶信息,那么數(shù)據(jù)庫(kù)是靠什么快速的在十萬(wàn)條數(shù)據(jù)中找到這條id為80000的用戶數(shù)據(jù)的呢?各位有沒(méi)有想過(guò)這個(gè)問(wèn)題?
其實(shí)底層就是索引在幫忙,做一個(gè)簡(jiǎn)單的比喻,如果我們把剛才的用戶表中的數(shù)據(jù)存放到一本字典中,那么索引就是這個(gè)字典的目錄,當(dāng)我們想要查找某一個(gè)數(shù)據(jù)的時(shí)候,數(shù)據(jù)庫(kù)底層不會(huì)傻乎乎的去一頁(yè)一頁(yè)從字典中翻找我們想要的數(shù)據(jù),而是直接去翻這根本字典的目錄,通過(guò)目錄精準(zhǔn)定位我們想要查詢的數(shù)據(jù)在哪一頁(yè),然后直接定位到那一頁(yè),將我們要查找的數(shù)據(jù)取出。
那么各位再想一下,如果字典沒(méi)有目錄。會(huì)是什么樣的結(jié)果呢?我們要查找用戶張三的信息,然后數(shù)據(jù)庫(kù)受到了指令,從第一頁(yè)開(kāi)始翻看查閱,沒(méi)有翻到第二頁(yè),有沒(méi)有翻到第三頁(yè)......各位想過(guò)沒(méi)有,這會(huì)多麻煩,所以,通過(guò)字典目錄提高查找效率是必須要做的,通過(guò)索引查找數(shù)據(jù)也是迫在眉睫的,但同樣,我們既然制造了目錄,如果我們對(duì)字典中的數(shù)據(jù)做了更改,目錄是不是也要改啊,這就為我們后續(xù)索引的缺點(diǎn)做了鋪墊。
總結(jié)來(lái)說(shuō)就一句話,之所以要使用索引,是因?yàn)樗饕梢蕴岣呶覀儾檎覕?shù)據(jù)的效率。
3. 索引的概述
MySQL官方給索引的定義是:索引(Index)是幫助MySQL高效獲取數(shù)據(jù)的有序的數(shù)據(jù)結(jié)構(gòu)。
簡(jiǎn)潔點(diǎn)來(lái)說(shuō),索引的本質(zhì)是數(shù)據(jù)結(jié)構(gòu)。
此外,索引是在存儲(chǔ)引擎中實(shí)現(xiàn)的,當(dāng)我們的存儲(chǔ)引擎不同時(shí),索引底層的數(shù)據(jù)結(jié)構(gòu)也不相同。
同時(shí),存儲(chǔ)引擎中還定義了一個(gè)表中最多不能超過(guò)16個(gè)索引,但其實(shí)我們也用不到這么多,所以完全可以放心;也定義了索引的長(zhǎng)度不能超過(guò)256個(gè)字節(jié),其實(shí)也很大了,我們通常不會(huì)把索引定義那么大,也可以放心。
4. 索引的優(yōu)點(diǎn)是什么?
4.1 降低數(shù)據(jù)庫(kù)的IO成本,提高數(shù)據(jù)查找效率
各位細(xì)想,我們的數(shù)據(jù)都是存儲(chǔ)在磁盤(pán)上的,只有我們?cè)谛枰臅r(shí)候才會(huì)從磁盤(pán)IO讀取到內(nèi)存中。那么用戶表的那本字典,如果沒(méi)有索引,我會(huì)就會(huì)先從磁盤(pán)IO加載第一頁(yè)的數(shù)據(jù)到內(nèi)存,然后遍歷數(shù)據(jù),發(fā)現(xiàn)沒(méi)有找到,再經(jīng)過(guò)IO磁盤(pán),加載第二頁(yè)的數(shù)據(jù)到內(nèi)存,再遍歷查找...... 各位知道,內(nèi)存中數(shù)據(jù)的處理速度是很快的,但是從磁盤(pán)加載到內(nèi)存這個(gè)速度就很慢了,假設(shè)我們遍歷一頁(yè)數(shù)據(jù)需要1ms,那么IO一次可能就需要50ms,這還只是加載了第一頁(yè)的數(shù)據(jù),那如果有很多很多頁(yè)數(shù)據(jù),查找速率絕對(duì)非常慢。但是如果我們使用了索引,我們只需要IO一次將索引加載到內(nèi)存中,遍歷索引,精準(zhǔn)找到數(shù)據(jù)所在的頁(yè)數(shù),然后第二次IO直接讀取是數(shù)據(jù)所在的那一頁(yè)數(shù)據(jù),這樣的話,只需要經(jīng)過(guò)兩次磁盤(pán)IO就可以得到我們需要的數(shù)據(jù)了。
4.2 保證數(shù)據(jù)庫(kù)每一行數(shù)據(jù)的唯一性
我們知道,表中通常都是有主鍵的,而且都會(huì)設(shè)置為非空且唯一,我們的索引也是一樣,索引也是具有唯一性的,通過(guò)創(chuàng)建唯一索引,可以保證數(shù)據(jù)庫(kù)表中每一行數(shù)據(jù)的唯一性。
4.3 加速表與表之間的連接
對(duì)于有父子關(guān)系的子表與父表在進(jìn)行聯(lián)合查詢時(shí),索引可以加速表與表之間的連接,從而提高表的查詢效率。
4.4 減少查詢中分組與排序的執(zhí)行時(shí)間
各位想一下,字典中的目錄我們都是按照一定的規(guī)律去進(jìn)行排列的,索引也是一樣的道理,從小到大把數(shù)據(jù)進(jìn)行排列,既然是已經(jīng)排好序的數(shù)據(jù)了,當(dāng)我們?cè)诓樵冋Z(yǔ)句中對(duì)數(shù)據(jù)進(jìn)行 ORDER BY 排序時(shí)候,其實(shí)已經(jīng)是有序的了,那么就省去了我們?cè)俅闻判虻臅r(shí)間,降低了CPU的消耗,而且以后查詢排序都不需要再去浪費(fèi)時(shí)間,是一個(gè)一勞永逸的做法。
?5. 索引的缺點(diǎn)是什么?
5.1 創(chuàng)建索引和維護(hù)索引非常耗費(fèi)時(shí)間
這一點(diǎn)我想不難理解,各位想,當(dāng)我們對(duì)一本書(shū)中的內(nèi)容做了更改之后,你是不是也要去更改目錄啊,特別是你的書(shū)內(nèi)容如果比較多,如果你刪除了幾頁(yè)內(nèi)容,其他的內(nèi)容都要跟著做調(diào)整;如果你一直往書(shū)中繼續(xù)寫(xiě)內(nèi)容,目錄也要跟著增多吧,所以說(shuō),索引的創(chuàng)建與維護(hù)也是非常浪費(fèi)時(shí)間的。
5.2 索引也是占用磁盤(pán)空間的
我們還把數(shù)據(jù)的內(nèi)容比作是一本書(shū),你書(shū)中的內(nèi)容需要用紙張去些內(nèi)容,那么目錄也要寫(xiě)在紙上??;而且索引本身就是數(shù)據(jù)結(jié)構(gòu),是需要占用磁盤(pán)的空間的,并且隨著你數(shù)據(jù)量的增大,你索引所占的磁盤(pán)空間大小很有可能會(huì)超過(guò)數(shù)據(jù)本身所占的磁盤(pán)文件大小。
5.3 索引會(huì)降低表的更新速度
其實(shí)這一點(diǎn)與維護(hù)索引是有一些內(nèi)在關(guān)聯(lián)的,如果我們對(duì)表中的數(shù)據(jù)做了動(dòng)態(tài)修改,那么索引也是會(huì)不斷地去動(dòng)態(tài)修改維護(hù)的,這就大大的降低了表的更新速度。
6. B+ 樹(shù)到底是什么樣的?
我們知道,樹(shù)是一種常見(jiàn)的數(shù)據(jù)結(jié)構(gòu),我們最常見(jiàn)的就是二叉樹(shù),還有平衡二叉樹(shù),還有二叉樹(shù)的升級(jí)版紅黑樹(shù)。
既然有二叉樹(shù),就有三叉樹(shù),N叉樹(shù)。
其實(shí),B+樹(shù)底層就是一個(gè)N叉樹(shù),這里我以三叉樹(shù)為例,模型如下圖所示
我們的最頂層就是根節(jié)點(diǎn),中間層是子節(jié)點(diǎn),最下層是葉子節(jié)點(diǎn)。
在數(shù)據(jù)庫(kù)底層 InnoDB 存儲(chǔ)引擎中,存儲(chǔ)數(shù)據(jù)和索引就是采用B+樹(shù)的形式存儲(chǔ)的。
7. 數(shù)據(jù)庫(kù)底層存儲(chǔ)數(shù)據(jù)的實(shí)質(zhì)
如上圖所示,是我們數(shù)據(jù)庫(kù)中常見(jiàn)的一張表,這里有很多的數(shù)據(jù),但是在底層,你們知道嗎,表的每一行數(shù)據(jù)都是單獨(dú)存儲(chǔ)的,在磁盤(pán)上的存儲(chǔ)位置是不連續(xù)的。因?yàn)槿绻覀儽碇械臄?shù)據(jù)有很多,達(dá)到了上萬(wàn)條數(shù)據(jù),那么在磁盤(pán)中不一定有那么大的一個(gè)連續(xù)空間,因此在數(shù)據(jù)庫(kù)底層,它的每一行數(shù)據(jù)都是單獨(dú)存儲(chǔ)的,而且還要滿足一個(gè)邏輯上的連續(xù),我們 employee_id = 100 的員工下一條記錄是 employee_id = 101 的員工這一點(diǎn)是不能變的,不能說(shuō)我查一次,它們的順序就變一次,一定要它們保持一定的邏輯連續(xù)性。
說(shuō)到這里各位應(yīng)該就明白了吧,沒(méi)錯(cuò)!就是鏈表,其實(shí)表中的每一行數(shù)據(jù)在磁盤(pán)底層是采用單向鏈表的形式來(lái)存儲(chǔ)的,這樣不僅解決了存儲(chǔ)空間大小不夠問(wèn)題,還解決了讓它們?cè)谶壿嬌蠞M足連續(xù)性的問(wèn)題。
就拿上面這張表舉例,在數(shù)據(jù)庫(kù)的底層,employee_id = 100 的員工就是鏈表的頭節(jié)點(diǎn),它內(nèi)部還會(huì)存儲(chǔ) employee_id = 101 的員工的地址值,從而找到該員工的全部相關(guān)信息,而employee_id = 101 的員工內(nèi)部也會(huì)存儲(chǔ) employee_id = 102 的員工的地址值,通過(guò)該地址值找到 employee_id = 102 的員工全部信息由此形成一個(gè)單向鏈表。
8. 索引的實(shí)現(xiàn)原理
如上圖,根據(jù)上面B+樹(shù)的結(jié)構(gòu),我們就能大致推演出索引的底層原理了,這里我化繁為簡(jiǎn),簡(jiǎn)單說(shuō)明。
我們先看最下層,最下層也是葉子節(jié)點(diǎn)頁(yè),這些葉子節(jié)點(diǎn)頁(yè)之間是使用雙向鏈表進(jìn)行連接的,而內(nèi)部的數(shù)據(jù)則是我剛剛說(shuō)的通過(guò)單向鏈表連接的,圖中已經(jīng)標(biāo)識(shí)出,而且每一頁(yè)中都有一個(gè)數(shù)組,存放主鍵的值,方便我們后續(xù)使用二分法快速確定要查找的數(shù)據(jù)。在B+樹(shù)索引中,所有的真實(shí)數(shù)據(jù)都是存放在葉子節(jié)點(diǎn)葉中的,如圖,在葉子節(jié)點(diǎn)中頁(yè)10中,下方的 (1,4,u);(3,9,d);(4,4,a) 就是一條條真實(shí)的數(shù)據(jù),上方的0就是標(biāo)記這些是數(shù)據(jù),假設(shè)1,3,4這些首字段為一條條數(shù)據(jù)的主鍵。
我們繼續(xù)推演,如果沒(méi)有中間層頁(yè)30與頁(yè)32,只有最下方一層,當(dāng)我們想要找想要查找主鍵為100的用戶信息時(shí),就需要從頁(yè)10開(kāi)始從頭遍歷這個(gè)雙向鏈表,在遍歷到頁(yè)9的時(shí)候,找到了主鍵為100的用戶,我們直接獲取到該用戶的信息,一共遍歷了3頁(yè),需要進(jìn)行三次磁盤(pán)IO。
如果我們加上中間層,為每一個(gè)葉子節(jié)點(diǎn)頁(yè)再創(chuàng)建一個(gè)目錄,即頁(yè)30與頁(yè)32,頁(yè)30下方存儲(chǔ)了1,10;5,28;12,9;209,20四條數(shù)據(jù)。這里的1,10中的1指代的就是葉子節(jié)點(diǎn)頁(yè)10中最小的一條數(shù)據(jù)1,10則是指代頁(yè)10,實(shí)際存儲(chǔ)的是頁(yè)10的內(nèi)存地址;5,28則是葉子節(jié)點(diǎn)頁(yè)28中的最小主鍵值5與該頁(yè)所在的地址值,這些數(shù)據(jù)上方都是1,表示他們?nèi)匀皇悄夸涰?yè)而不是真實(shí)的數(shù)據(jù)...... 我們使用了這個(gè)中間目錄頁(yè)之后,我們?cè)偃カ@取主鍵為100的用戶數(shù)據(jù)時(shí),就會(huì)先去遍歷中間頁(yè),在中間頁(yè)中確定主鍵值為100的用戶數(shù)據(jù)存放在頁(yè)9中,就可以直接將頁(yè)9中的數(shù)據(jù)全部讀取到內(nèi)存,然后再去尋找該用戶的數(shù)據(jù),可以發(fā)現(xiàn),此時(shí)加上了中間頁(yè)之后,只需要兩次IO,節(jié)約了時(shí)間,提高了查找效率。
我們繼續(xù)類比推理,如果以后我的數(shù)據(jù)越來(lái)越多,中間也最多只能存放四個(gè)葉子節(jié)點(diǎn)頁(yè)的數(shù)據(jù),再多就存不下了,這個(gè)時(shí)候,我們就需要繼續(xù)創(chuàng)建新的中間頁(yè),而如果中間頁(yè)一直增多,就會(huì)面臨和剛才葉子節(jié)點(diǎn)頁(yè)一樣的問(wèn)題。
這個(gè)時(shí)候,我們就可以套娃式的去再創(chuàng)建一個(gè)中間頁(yè)的頂層也,讓這個(gè)頂層頁(yè)使用類似的手段存放中間頁(yè)的重要數(shù)據(jù)。舉例說(shuō)明,我們想要查找主鍵為300的用戶數(shù)據(jù),第一次磁盤(pán)IO,我們把頁(yè)33的全部數(shù)據(jù)加載到內(nèi)存中,經(jīng)過(guò)二分查找判斷發(fā)現(xiàn)與主鍵為300的用戶數(shù)據(jù)應(yīng)存放在子頁(yè)30中,在然后進(jìn)行第二次IO,將子夜30的全部數(shù)據(jù)加載到內(nèi)存中,再經(jīng)過(guò)二分查找判斷,確定主鍵為300的用戶數(shù)據(jù)存應(yīng)放在葉子節(jié)點(diǎn)頁(yè)20中,在進(jìn)行第三次磁盤(pán)IO,將葉子節(jié)點(diǎn)頁(yè)20中的數(shù)據(jù)全部加載到內(nèi)存中,再經(jīng)過(guò)二分查找,快速定位到主鍵為300的用戶所在的內(nèi)存地址,獲取到信息,此時(shí)只需要經(jīng)歷三次磁盤(pán)IO,而且非常穩(wěn)定,當(dāng)我們想要查詢某條數(shù)據(jù)是,都是進(jìn)行三次磁盤(pán)IO就可以獲取到結(jié)果,找得到或找不到都要返回,極大的保證了我們查找數(shù)據(jù)時(shí)的穩(wěn)定性。
9. 為什么B+樹(shù)的高度不建議超過(guò)四層?
在剛才我舉得例子中,各位應(yīng)該看得出來(lái),我所舉例的B+樹(shù)是三層,絕大多數(shù)情況下都是三層,最多四層。而在面試的時(shí)候,有些面試官會(huì)問(wèn)你為什么B+樹(shù)的不建議超過(guò)四層,請(qǐng)給出具體的原因。
原因如下:
不知道各位發(fā)現(xiàn)了沒(méi)有,當(dāng)我們的B+樹(shù)只有兩層時(shí),我們最多就可以經(jīng)過(guò)兩次磁盤(pán)IO就可以查找到數(shù)據(jù),當(dāng)B+樹(shù)是三層時(shí),我們進(jìn)行三次磁盤(pán)IO就可以獲取到數(shù)據(jù)。這也從側(cè)面印證了一點(diǎn),我們與磁盤(pán)交互的次數(shù),是受到B+樹(shù)高度的影響的,各位都知道,內(nèi)存中處理數(shù)據(jù)速度是非??斓?,二分查找數(shù)據(jù)可能就1ms~3ms左右的時(shí)間,而與磁盤(pán)IO就不一樣了,磁盤(pán)IO一次可能就需要花費(fèi)100ms,每交互一次100ms,這個(gè)效率是非常低的,因此我們應(yīng)當(dāng)盡量減少與磁盤(pán)的IO次數(shù),這就要求我們B+樹(shù)的高度不能過(guò)高,最好三層,不能超過(guò)四層。
那肯定會(huì)有人問(wèn),問(wèn)什么非要是三層或四層,我選五層不行嗎?
其實(shí)從原則上來(lái)講也是可以的,但是我們還要考慮數(shù)據(jù)的存儲(chǔ)量,這里給各位說(shuō)一下,數(shù)據(jù)庫(kù)的底層一個(gè)真實(shí)的數(shù)據(jù)頁(yè)大小是16KB,也就是 16000 個(gè)字節(jié),假設(shè)我們的每條數(shù)據(jù)占用160個(gè)字節(jié),160個(gè)字節(jié)已經(jīng)不小了,完全夠用,那么我們每個(gè)葉子節(jié)點(diǎn)頁(yè)就可以存儲(chǔ) 16000/160 = 100 條數(shù)據(jù)。而我們的非葉子節(jié)點(diǎn)頁(yè),因?yàn)椴](méi)有存儲(chǔ)真實(shí)的用戶數(shù)據(jù),所以可以存儲(chǔ)更多的內(nèi)容,假設(shè)我們的目錄頁(yè)可以存儲(chǔ) 1000 條葉子節(jié)點(diǎn)頁(yè)數(shù)據(jù),當(dāng)我們的B+樹(shù)正好滿兩層的時(shí)候,存儲(chǔ)的數(shù)據(jù)量是 100 * 1000 = 100,000 十萬(wàn)條數(shù)據(jù);當(dāng)我們 B+ 樹(shù)滿三層時(shí),存儲(chǔ)的數(shù)據(jù)量是 100 * 1000 * 1000 = 100,000,000 一億條數(shù)據(jù);讓我們的B+樹(shù)滿四層時(shí),存儲(chǔ)的數(shù)據(jù)量是 100 * 1000 * 1000 * 1000 = 100,000,000,000 一千億條數(shù)據(jù),可以看到,當(dāng)B+樹(shù)四層的時(shí)候,數(shù)據(jù)量已經(jīng)非??植懒?,而且我們也不可能在一張表中存儲(chǔ)這么多數(shù)據(jù)。
而在實(shí)際開(kāi)發(fā)業(yè)務(wù)的時(shí)候,我們會(huì)采用微服務(wù)的架構(gòu),并對(duì)數(shù)據(jù)庫(kù)進(jìn)行分庫(kù)分表,進(jìn)行讀寫(xiě)分離,不會(huì)讓大量數(shù)據(jù)在同一張表中存儲(chǔ),既不安全,效率又低。因此,我們通常會(huì)把B+樹(shù)控制在三層左右,此時(shí)一張表就足以存儲(chǔ)一億條數(shù)據(jù),而且只需要三次磁盤(pán)IO就可以查找到想要的數(shù)據(jù),綜合考慮是最優(yōu)的選擇。如果像阿里京東騰訊這些用戶量多余1億的企業(yè),也有可能會(huì)采用四層B+樹(shù),但其實(shí)還是是很少這么做的。這就是為什么要把B+樹(shù)的高低控制在四層以下,總的來(lái)說(shuō)就一句話,三層或四層B+樹(shù),既滿足了數(shù)據(jù)量的存儲(chǔ)需要,又達(dá)到了最佳查找效率。
10. 什么是聚簇索引?
聚簇索引,又名主鍵索引,它是基于表的主鍵而生成的索引,而且它不是一種索引的類型,而是一種數(shù)據(jù)的存儲(chǔ)方式(表中所有的用戶數(shù)據(jù)都會(huì)存放在葉子節(jié)點(diǎn)中)如下圖所示就是一個(gè)經(jīng)典的聚簇索引。在 InnoDB 引擎中,聚簇索引是不需要我們手動(dòng)創(chuàng)建的,在我們創(chuàng)建好表結(jié)構(gòu)的時(shí)候,數(shù)據(jù)庫(kù)的底層已經(jīng)為我們生成聚簇索引,并隨著我們數(shù)據(jù)的添加,索引會(huì)一直不斷的變化和維護(hù)。
10.1 聚簇索引的特點(diǎn)
聚簇索引還有一些特點(diǎn),只有滿足以下四點(diǎn)的索引才能稱為聚簇索引:
1. 每個(gè)頁(yè)內(nèi)的記錄是按照的主鍵的大小順序形成的一個(gè)單向鏈表,例如頁(yè)10中的(1,4,u)與(3,9,d)便是通過(guò)單向鏈表連接的;
2. 各個(gè)存放的數(shù)據(jù)頁(yè)之間采用的是雙向鏈表進(jìn)行連接,如圖中的頁(yè)10與頁(yè)28之間就是雙向鏈表;
3. 存放目錄項(xiàng)記錄的頁(yè)(即剛才我所提的中間層)它們之間也是采用雙向鏈表的形式進(jìn)行連接,如圖中的頁(yè)30與頁(yè)32就是雙向鏈表;
4. B+ 樹(shù)中存放的是完整的表數(shù)據(jù),如圖中頁(yè)10的(1,4,u),(3,9,d)等,這里我列的簡(jiǎn)單,只有三個(gè)字段,實(shí)際聚簇索引存儲(chǔ)的數(shù)據(jù)會(huì)以你定義的表中的字段為主;
10.2 聚簇索引的優(yōu)點(diǎn)?
(1)因?yàn)榫鄞厮饕袑⑺饕c所有的表數(shù)據(jù)都存儲(chǔ)在了B+樹(shù)種,所以聚簇索引比非聚簇索引查詢效率要高;
(2)因?yàn)榫鄞厮饕腔谥麈I大小排列的索引,所以對(duì)于主鍵的范圍查找與排序查找速度非???/strong>;
(3)因?yàn)榫鄞厮魇且凑找欢ǖ捻樞蜻M(jìn)行排列,再查詢顯示一定范圍的數(shù)據(jù)的時(shí)候,由于數(shù)據(jù)是緊密相連的,數(shù)據(jù)庫(kù)就不需要從不同的區(qū)塊中去提取數(shù)據(jù),節(jié)省了大量的IO操作。
10.3 聚簇索引的缺點(diǎn)?
(1)插入的速度嚴(yán)重依賴于插入的順序。因?yàn)榫鄞厮饕前凑罩饾u的順序去建立的,如果我們不按照順序去插入,而是突然插入一個(gè)主鍵值大的數(shù)據(jù),又突然插入一個(gè)主鍵值小的數(shù)據(jù),這就會(huì)導(dǎo)致索引會(huì)不斷地去動(dòng)態(tài)變化,還可能出現(xiàn)頁(yè)分裂,嚴(yán)重影響性能,因此對(duì)于 InnoDB 引擎來(lái)說(shuō),建議在插入數(shù)據(jù)的時(shí)候按照順序插入或者使用主鍵自增策略。
(2)更新主鍵的代價(jià)極高。因?yàn)榫鄞厮饕褪腔趲准傻?,如果我們?duì)主鍵做出更改,那么整個(gè)索引結(jié)構(gòu)可能都要進(jìn)行更改,而進(jìn)行更改是非常耗費(fèi)性能的,因此對(duì)于 InnoDB 引擎來(lái)說(shuō),我們一般最好把主鍵定義為不可更改。
10.4 聚簇索引的補(bǔ)充說(shuō)明
(1)MySQL 數(shù)據(jù)庫(kù)目前只有 InnoDB 引擎支持聚簇索引,MyISAM 引擎不支持聚簇索引;
(2)由于數(shù)據(jù)物理存儲(chǔ)排序方式只有一種,所以每個(gè) MySQL 表只能有一個(gè)聚簇索引,一般情況下都是該表的主鍵;
(3)如果我們沒(méi)有給表定義主鍵,那么 InnoDB 會(huì)選擇一個(gè)非空且唯一的字段替代,如果也沒(méi)有這樣的字段,那么 InnoDB 會(huì)隱世式定義一個(gè)主鍵作為聚簇索引;
(4)為了充分利用聚簇索引的特性, InnoDB 引擎下在定義表的主鍵的時(shí)候,不建議使用無(wú)序的id,例如UUID,MD5,HASH,字符串等作為主鍵,否則無(wú)法保證數(shù)據(jù)的順序增長(zhǎng)。
11. 什么是非聚簇索引?
非聚簇索引,也可以叫二級(jí)索引,或叫輔助索引。
剛才我們說(shuō)到了,聚簇索引是 InnoDB 引擎基于主鍵自動(dòng)生成的,可以提高有關(guān)主鍵的查找速度和查找范圍,也就是說(shuō)只有搜索條件中包含主鍵字段時(shí)才有效果。那么當(dāng)我們不使用主鍵字段來(lái)查找數(shù)據(jù)而是基于普通字段查找數(shù)據(jù)同時(shí)又想提高查找效率的時(shí)候,我們就可以通過(guò)普通字段定義非聚簇索引。如下圖所示就是一個(gè)非聚簇索引所構(gòu)成的 B+ 樹(shù)。
這里需要注意一點(diǎn),非聚簇索引構(gòu)成的 B+ 樹(shù)與聚簇索引構(gòu)成的 B+ 樹(shù)稍有不同。
這里目錄頁(yè)44與子目錄頁(yè)42,43的原理與聚簇索引中類似,但葉子節(jié)點(diǎn)中就不一樣了,剛才我說(shuō)到了,聚簇索引中,存放著完整的表數(shù)據(jù),而在非聚簇索引中,葉子節(jié)點(diǎn)中只存放著索引對(duì)應(yīng)的那個(gè)普通字段與主鍵值。如圖中,藍(lán)色對(duì)應(yīng)的就是普通字段,黃色對(duì)應(yīng)的就是主鍵值,我們可以看一下,在前面那張圖中,頁(yè)9中存放這一條數(shù)據(jù)(20,2,e),正好對(duì)應(yīng)著我們這里的非聚簇索引,這里頁(yè)34下面藍(lán)色方塊中值為2的數(shù)據(jù)對(duì)應(yīng)的主鍵值也為20,即(2,20)。這樣說(shuō)各位同學(xué)應(yīng)該更好理解一些,各位可以將上圖與前面聚簇索引中的字段值做對(duì)比就可以看出來(lái),我就不一一舉例說(shuō)明了。
12. 什么是回表?
剛才我說(shuō)到了,在非聚簇索引中,葉子節(jié)點(diǎn)只存放了該索引字段的相關(guān)信息和主鍵信息,那么只有這兩項(xiàng)信息肯定是不夠的,如果我們的目的是通過(guò)非聚簇索引查詢某條用戶的全部信息,它就會(huì)進(jìn)行回表操作,什么是回表呢?
其實(shí)很簡(jiǎn)單,當(dāng)我們使用非聚簇索引的字段作為查詢條件去查詢表中的相關(guān)信息時(shí),數(shù)據(jù)庫(kù)會(huì)先根據(jù)非聚簇索引形成的 B+ 樹(shù),查詢到該字段對(duì)應(yīng)的主鍵值,再根據(jù)主鍵值去聚簇索引形成的 B+ 樹(shù)中查詢完整的用戶信息。這個(gè)過(guò)程中,需要先查詢非聚簇索引的 B+ 樹(shù),再去查詢聚簇索引的 B+ 樹(shù),這個(gè)過(guò)程就叫做回表。
舉個(gè)栗子,現(xiàn)在有一張表 user ,主鍵?uid ,手機(jī)號(hào)碼 phone,用戶名?username,密碼 password四個(gè)字段,我們給手機(jī)號(hào)碼字段創(chuàng)建了非聚簇索引,那么它的底層就會(huì)只存儲(chǔ)手機(jī)號(hào)碼信息和所對(duì)應(yīng)的主鍵uid 信息,當(dāng)我們通過(guò) 手機(jī)號(hào)去查詢用戶完整信息時(shí),語(yǔ)句應(yīng)該是 SELECT * FROM user WHERE phone = "待查詢用戶手機(jī)號(hào)";在這個(gè)查詢語(yǔ)句中,它會(huì)先去非聚簇索引形成的 B+ 樹(shù)通過(guò) phone 查找到對(duì)應(yīng)的主鍵uid,查找到主鍵uid的信息之后,它會(huì)再去聚簇索引的 B+ 樹(shù)中通過(guò) uid 查詢出完整的用戶信息并返回,這就是它在底層的執(zhí)行邏輯,先查非聚簇索引,再回表查聚簇索引。
13. 為什么非聚簇索引的葉子節(jié)點(diǎn)不存儲(chǔ)一份完整的數(shù)據(jù)方便查找呢?
這個(gè)問(wèn)題其實(shí)也算是一個(gè)面試題。我剛才說(shuō)了什么是回表,但是不知道各位發(fā)現(xiàn)一個(gè)問(wèn)題沒(méi)有,回表還需要再查尋一次 B+ 樹(shù),這樣不是很麻煩嗎?我們直接將用戶的全部信息在非聚簇索引的 B+ 樹(shù)中葉存儲(chǔ)一份多省事啊,怎么不這樣做呢?
如果你也有這樣的問(wèn)題,說(shuō)明你認(rèn)真思考了。大家想一想,在聚簇索引中,我們已經(jīng)存儲(chǔ)了一份完整的表數(shù)據(jù),還有 B+ 樹(shù)的結(jié)構(gòu),這些都是非常占用磁盤(pán)滿空間的,而非聚簇索引又不止可以創(chuàng)建一個(gè),我們可以創(chuàng)建多個(gè),剛才我舉的例子中只有四個(gè)字段,在實(shí)際的業(yè)務(wù)場(chǎng)景中,二十個(gè)字段也是很有可能的,如果我們?yōu)榱朔奖悴檎矣謩?chuàng)建了三個(gè)非聚簇索引,并且圖方便在非聚簇索引中也存儲(chǔ)完整的用戶數(shù)據(jù),各位同學(xué)想一想,這樣是不是非常冗余,我們的用戶數(shù)據(jù)明明存儲(chǔ)一份就夠了,但是你創(chuàng)建一個(gè)非聚簇索引,數(shù)據(jù)就多存儲(chǔ)一份,再創(chuàng)建一個(gè),再存儲(chǔ)一份,我們創(chuàng)建三個(gè)非聚簇索引,那么加上聚簇索引我們底層的數(shù)據(jù)足足存儲(chǔ)了四份,非常非常冗余,這是絕對(duì)不允許的。另外,如果我們把全部的數(shù)據(jù)都存在非聚簇索引中,那么當(dāng)我們要對(duì)用戶數(shù)據(jù)作出修改時(shí),有多少個(gè)索引,我們就要修改多少次,各位想是不是,我們對(duì)用戶張三做修改,所有的索引中關(guān)于張三的信息都要做修改,非常不劃算不值得。所以不在非聚簇索引上也存儲(chǔ)一份完整的用戶數(shù)據(jù),一方面是為了防止數(shù)據(jù)大量冗余,造成磁盤(pán)資源的浪費(fèi);另一方面是防止多余的操作。
14. 聚簇索引與非聚簇索引的區(qū)別?
(1)聚簇索引葉子節(jié)點(diǎn)存儲(chǔ)的是完整的用戶數(shù)據(jù),而非聚簇索引中存儲(chǔ)的只有主鍵與索引字段數(shù)據(jù),非聚簇索引不會(huì)影響表的物理存儲(chǔ)結(jié)構(gòu);
(2)一個(gè)表只能有一個(gè)聚簇索引,但是可以有多個(gè)非聚簇索引;
(3)使用聚簇索引時(shí),查詢效率很高,但如果是對(duì)數(shù)據(jù)進(jìn)行增刪改操作,效率會(huì)比非聚簇索引低;
15. 什么是聯(lián)合索引?
說(shuō)的準(zhǔn)確一些,聯(lián)合索引其實(shí)屬于是非聚簇索引中的一種,它與我們說(shuō)的聯(lián)合主鍵是有一些類似的,剛才我們說(shuō)的非聚簇索引是使用一個(gè)字段構(gòu)成 B+ 樹(shù),而有些時(shí)候我們會(huì)把數(shù)據(jù)庫(kù)中的兩個(gè)字段都作為索引,這種情況下形成的索引就叫聯(lián)合索引。文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-651193.html
舉個(gè)例子,假設(shè)一張學(xué)生表 student ,有主鍵字段C1,非主鍵字段C2,C3,C4,C5?,F(xiàn)在我們?yōu)镃2與C3創(chuàng)建聯(lián)合索引,那么形成的 B+ 樹(shù)中就會(huì)存放 C2,C3,C1字段,并按照從上到下的順序排列,會(huì)先比較聯(lián)合字段C2的大小,先按C2大小排序,當(dāng)C2大小相同時(shí),再根據(jù)C3大小排序,在下方存貯主鍵C1的值,從而形成一個(gè)聯(lián)合索引 B+ 樹(shù)。文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-651193.html
到了這里,關(guān)于深入理解索引B+樹(shù)的基本原理的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!