索引的概念基本所有人都會遇到過,就算沒有了解過數(shù)據(jù)庫中的索引袍嬉,在生活中也不可避免的接觸到境蔼。比方說書籍的目錄灶平,字典的查詢頁,圖書館的科目檢索等等箍土。其實這些都是一種索引逢享,并且所起到的作用大同小異。
而對于數(shù)據(jù)庫而言吴藻,只不過是將索引的概念抽象出來瞒爬,讓建立索引的過程更為靈活而自由,從而可以在不同的場景下優(yōu)化數(shù)據(jù)庫的查詢效率沟堡。
索引在數(shù)據(jù)庫的實際應用場景中十分普遍侧但,數(shù)據(jù)庫的優(yōu)化也離不開對索引的優(yōu)化。同時航罗,索引相關(guān)的知識也是面試高頻的考點之一禀横,是應試者理論結(jié)合現(xiàn)實最為直接的體現(xiàn)。
因此粥血,本文將從基礎(chǔ)理論出發(fā)柏锄,介紹 MySQL 按照邏輯角度的索引分類和實現(xiàn),通過數(shù)據(jù)結(jié)構(gòu)的實現(xiàn)原理闡述不同結(jié)構(gòu)對建立索引帶來的優(yōu)劣勢复亏,同時針對物理存儲的方式對索引的組織特點和應用場景進行分析趾娃。最后根據(jù)不同的應用場景盡可能的探究如何建立起高性能的索引。
概念
什么是索引
索引似乎并沒有十分明確的定義缔御,更多的是一種定性的描述抬闷。簡單來講,索引就是一種將數(shù)據(jù)庫中的記錄按照特殊形式存儲的數(shù)據(jù)結(jié)構(gòu)耕突。通過索引笤成,能夠顯著地提高數(shù)據(jù)查詢的效率,從而提升服務器的性能有勾。
專業(yè)一點來說呢疹启,索引是一個排好序的列表古程,在這個列表中存儲著索引的值和包含這個值的數(shù)據(jù)所在行的物理地址蔼卡。在數(shù)據(jù)庫十分龐大的時候,索引可以大大加快查詢的速度挣磨,這是因為使用索引后可以不用掃描全表來定位某行的數(shù)據(jù)雇逞,而是先通過索引表找到該行數(shù)據(jù)對應的物理地址然后訪問相應的數(shù)據(jù)。
說起索引茁裙,其實并不是 MySQL 數(shù)據(jù)庫特有的機制塘砸,在關(guān)系型數(shù)據(jù)庫中都會有類似不同的實現(xiàn)。這里我們也只是討論 MySQL 數(shù)據(jù)庫中的索引實現(xiàn)晤锥。
事實上掉蔬,說是 MySQL 的索引其實并不準確。因為在 MySQL 中,索引是在存儲引擎層而不是服務器層實現(xiàn)的二庵。這意味著我們所討論的索引準確來說是 InnoDB 引擎或 MyISAM 引擎或其它存儲引擎所實現(xiàn)的乾颁。
所以索引即便是在MySQL中也沒有統(tǒng)一的標準,不同存儲引擎的所實現(xiàn)的索引工作方式也并不一樣蛉迹。不是所有的存儲引擎都支持相同類型的索引傅寡,即便是多個引擎支持同一種類型的索引,其底層的實現(xiàn)也可能不同北救。
為什么需要索引
說了這么多荐操,索引似乎就是給數(shù)據(jù)庫添加了一個「目錄頁」,能夠方便查詢數(shù)據(jù)珍策。但是索引的作用就僅此而已了嗎托启,為什么需要大費周章的建立并優(yōu)化索引?
說個題外話膛壹,我其實查字典從來都不喜歡查目錄頁驾中,無論是查中文還是英文。因為覺得那樣很慢模聋,一個個找索引肩民,效率很低。我習慣用的方式就是直接翻開字典链方,根據(jù)翻開的位置進行前后調(diào)整持痰。比方說我想找「醬 JIANG」字,會先隨機翻到一頁祟蚀,可能是「F」開頭工窍,在「J」前面,就往后翻一點前酿;如果隨機翻到「L」患雏,那就往前翻一點。重復直至找到罢维。
這大概就是類似于二分查找的方式淹仑,看起來好像是擺脫了索引的束縛,并且也能夠獲得比較高的查詢效率肺孵。但是其實轉(zhuǎn)念一想匀借,在計算機的運行處理中,「一個個找索引」這個過程其實非称骄剑快吓肋,不能跟我們手動比對偏旁部首的效率相提并論。同時瑰艘,為什么我可以直接翻開字典根據(jù)字母進行調(diào)整呢是鬼,這其實不就是因為我的腦子里存在一個大概的「索引表」肤舞,知道每個字母大概對應于字典的哪一個位置。雖然是模糊的均蜜,但卻是真實存在的萨赁。(好不容易強行解釋了一波...)
如此一來,可以看出索引的一大好處是如其概念中所提及的兆龙,使用索引后可以不用掃描全表來定位某行的數(shù)據(jù)杖爽,而是先通過索引表找到該行數(shù)據(jù)對應的物理地址然后訪問相應的數(shù)據(jù)。這樣的方式自然減少了服務器在響應時所需要對數(shù)據(jù)庫掃描的數(shù)據(jù)量紫皇。
不僅如此慰安,在執(zhí)行數(shù)據(jù)庫的范圍查詢時,若不使用索引聪铺,那么MySQL會先掃描數(shù)據(jù)庫的所有行數(shù)據(jù)并從中篩選出目標范圍內(nèi)的行記錄化焕,將這些行記錄進行排序并生成一張臨時表,然后通過臨時表返回用戶查詢的目標行記錄铃剔。這個過程會涉及到臨時表的建立和行記錄的排序撒桨,當目標行記錄較多的時候,會大大影響范圍查詢的效率键兜。
所以當添加索引時凤类,由于索引本身具有的順序性,使得在進行范圍查詢時普气,所篩選出的行記錄已經(jīng)排好序谜疤,從而避免了再次排序和需要建立臨時表的問題。
同時现诀,由于索引底層實現(xiàn)的有序性夷磕,使得在進行數(shù)據(jù)查詢時,能夠避免在磁盤不同扇區(qū)的隨機尋址仔沿。使用索引后能夠通過磁盤預讀使得在磁盤上對數(shù)據(jù)的訪問大致呈順序的尋址坐桩。這本質(zhì)上是依據(jù)局部性原理所實現(xiàn)的。
局部性原理:當一個數(shù)據(jù)被用到時封锉,其附近的數(shù)據(jù)也通常會馬上被使用绵跷。程序運行期間所需要的數(shù)據(jù)通常比較集中。由于磁盤順序讀取的效率很高(不需要尋道時間烘浦,只需很少的旋轉(zhuǎn)時間) 抖坪,因此對于具有局部性的程序來說萍鲸,磁盤預讀可以提高I/O效率闷叉。
磁盤預讀要求每次都會預讀的長度一般為頁的整數(shù)倍。而且數(shù)據(jù)庫系統(tǒng)將一個節(jié)點的大小設(shè)為等于一個頁脊阴,這樣每個節(jié)點只需要一次I/O就可以完全載入握侧。這里的頁是通過頁式的內(nèi)存管理所實現(xiàn)的蚯瞧,概念在這里簡單提一嘴。
分頁機制就是把內(nèi)存地址空間分為若干個很小的固定大小的頁品擎,每一頁的大小由內(nèi)存決定埋合。這樣做是為了從虛擬地址映射到物理地址,提高內(nèi)存和磁盤的利用率萄传。
所以呢甚颂,總結(jié)一下。索引的存在具有很大的優(yōu)勢秀菱,主要表現(xiàn)為以下三點:
- 索引大大減少了服務器需要掃描的數(shù)據(jù)量
- 索引可以幫助服務器避免排序和臨時表
- 索引可以將隨機I/O變成順序I/O
以上三點能夠大大提高數(shù)據(jù)庫查詢的效率振诬,優(yōu)化服務器的性能。因此一般來說衍菱,為數(shù)據(jù)庫添加高效的索引對數(shù)據(jù)庫進行優(yōu)化的重要工作之一赶么。
不過,凡事都有兩面性脊串。索引的存在能夠帶來性能的提升辫呻,自然在其它方面也會付出額外的代價。
索引本身以表的形式存儲琼锋,因此會占用額外的存儲空間放闺;
索引表的創(chuàng)建和維護需要時間成本,這個成本隨著數(shù)據(jù)量增大而增大缕坎;
構(gòu)建索引會降低數(shù)據(jù)的修改操作(刪除雄人,添加,修改)的效率念赶,因為在修改數(shù)據(jù)表的同時還需要修改索引表础钠;
所以對于非常小的表而言,使用索引的代價會大于直接進行全表掃描叉谜,這時候就并不一定非得使用索引了旗吁。沒辦法,成年人的世界總是這么的趨利避害停局。
邏輯分類
從邏輯的角度來對索引進行劃分的話很钓,可以分為單列索引、全文索引董栽、組合索引和空間索引码倦。其中單列索引又可分為主鍵索引、唯一索引和普通索引锭碳。這里的邏輯可以理解為從SQL語句的角度袁稽,或者是從數(shù)據(jù)庫關(guān)系表的角度。下面就簡單介紹這些索引的作用和用法擒抛,以及在修改表的時候如何添加索引推汽。
主鍵索引
即主索引补疑,根據(jù)主鍵建立索引,不允許重復歹撒,不允許空值莲组;
主鍵:數(shù)據(jù)庫表中一列或列組合(字段)的值,可唯一標識表中的每一行暖夭。
加速查詢 + 列值唯一(不可以有null)+ 表中只有一個
ALTER TABLE 'table_name' ADD PRIMARY KEY pk_index('col');
唯一索引
用來建立索引的列的值必須是唯一的锹杈,允許空值。唯一索引不允許表中任何兩行具有相同的索引值迈着。比方說嬉橙,在 employee
表中職員的姓 name
上創(chuàng)建了唯一索引,那么就表示任何兩個員工都不能同姓寥假。
加速查詢 + 列值唯一(可以有null)
ALTER TABLE 'table_name' ADD UNIQUE index_name('col');
普通索引
用表中的普通列構(gòu)建的索引市框,沒有任何限制。
僅加速查詢
ALTER TABLE 'table_name' ADD INDEX index_name('col');
全文索引
用大文本對象的列構(gòu)建的索引
ALTER TABLE 'table_name' ADD FULLTEXT INDEX ft_index('col');
組合索引
用多個列組合構(gòu)建的索引糕韧,這多個列中的值不允許有空值枫振。
多列值組成一個索引,專門用于組合搜索萤彩,其效率大于索引合并粪滤。
ALTER TABLE 'table_name' ADD INDEX index_name('col1','col2','col3');
在對多列組合建立索引時,會遵循「最左前綴」原則雀扶。
最左前綴原則:顧名思義杖小,就是最左優(yōu)先,上例中我們創(chuàng)建了 (col1, col2, col3) 多列索引,相當于創(chuàng)建了 (col1) 單列索引愚墓,(col1, col2) 組合索引以及 (col1, col2, col3) 組合索引予权。
所以當我們在創(chuàng)建多列索引時,要根據(jù)業(yè)務場景浪册,將 where 子句中使用最頻繁的一列放在最左邊扫腺。
空間索引
對空間數(shù)據(jù)類型的字段建立的索引,底層可通過 R 樹實現(xiàn)村象。只不過使用較少笆环,了解即可。
實現(xiàn)原理
我們知道厚者,索引的底層本身就是通過數(shù)據(jù)結(jié)構(gòu)來進行實現(xiàn)的躁劣。那么根據(jù)其底層的結(jié)構(gòu),常見的索引類型可分為哈希索引库菲,BTree 索引账忘,B+Tree 索引等。這里我們就主要來介紹這三種索引背后的實現(xiàn)機制。
哈希索引
顧名思義闪萄,哈希索引是通過哈希表實現(xiàn)的。哈希表的特點在之前的文章「九大數(shù)據(jù)結(jié)構(gòu)」中已經(jīng)詳細介紹過奇颠。通過哈希表的鍵值之間的對應關(guān)系败去,能夠在查詢時精確匹配索引的所有列。哈希索引將所有的根據(jù)索引列計算出來的哈希碼存儲在索引中烈拒,同時將指向每個數(shù)據(jù)行的指針保存在哈希表中圆裕。
上圖是通過哈希索引查詢行數(shù)據(jù)的示意圖,可以發(fā)現(xiàn)哈希索引同樣會發(fā)生哈希沖突荆几,并且是通過鏈地址法解決沖突的吓妆。當發(fā)送沖突時,還需要對鏈表進行遍歷對比吨铸,才能夠找到最終的結(jié)果行拢。
在 MySQL 中,只有 Memory 存儲引擎顯式的支持哈希索引诞吱,而innodb是隱式支持哈希索引的舟奠。
這里的隱式支持是指,innodb引擎有一個特殊的功能 “自適應哈希索引”房维,當innodb注意到一些索引值被使用的非常頻繁時沼瘫,且符合哈希特點(如每次查詢的列都一樣),它會在內(nèi)存中基于 B-Tree 索引之上再創(chuàng)建一個哈希索引咙俩。這樣就讓 BTree 索引也具有哈希索引的一些有點耿戚。這是一個完全自動的、內(nèi)部的行為阿趁。
由于哈希結(jié)構(gòu)的特殊性膜蛔,其用于非常高的檢索效率,通過哈希函數(shù)的映射可以一步到位脖阵。但是同樣也是因為結(jié)構(gòu)的特殊飞几,導致哈希索引只適用于某些特定的場合。哈希索引的限制[1]:
- 不支持范圍查詢独撇,比如
WHERE a > 5
屑墨;只支持等值比較查詢,包括=
纷铣、IN
卵史、<=>
- 無法被用來避免數(shù)據(jù)的排序操作;因為經(jīng)過了哈希函數(shù)的映射過程搜立,使得丟失了哈希前后的大小關(guān)系以躯,從而無法按照索引值的順序存儲。
- 不支持部分索引列的匹配查找,因為哈希索引始終使用索引列的全部內(nèi)容來計算哈希值忧设。例如在數(shù)據(jù)列 (A, B) 上建立哈希索引刁标,如果查詢只有數(shù)據(jù)列 A,則無法使用該索引址晕。
- 無法避免表掃描膀懈。因為當出現(xiàn)哈希沖突的時候,存儲引擎必須遍歷鏈表(拉鏈法)中所有的行指針谨垃,逐行進行比較启搂,直到找到所有符合條件的行。
- 哈希沖突很多的情況下刘陶,其索引維護的代價很高胳赌,并且性能并不一定會比 BTree 索引高。
BTree 索引
BTree 實際上是一顆多叉平衡搜索樹匙隔。從名字可以看出疑苫,BTree 首先是一顆多叉搜索樹,這意味著它是具有順序的纷责;其次 BTree 還是平衡的缀匕,這意味著它的左右子樹高度差小于等于1。
事實上一顆 BTree 需要滿足以下幾個條件:
每個葉子結(jié)點的高度都是一樣的碰逸;
每個非葉子結(jié)點由 n-1 個 key 和 n 個指針 point 組成乡小,其中 d<=n<=2d, key 和 point 相互間隔,結(jié)點兩端一定是 key饵史;
葉子結(jié)點指針都為 null满钟;
非葉子結(jié)點的key都是 [key,data] 二元組,其中 key 表示作為索引的鍵胳喷,data 為鍵值所在行的數(shù)據(jù)湃番;
一顆常見的BTree樹見下圖。(粉粉嫩嫩的圖吭露,不知道擊中你了沒_)
這是一顆三階的BTree吠撮,可通過鍵值的大小排序進行數(shù)據(jù)的查詢和檢索,其中葉子節(jié)點的指針都為空讲竿,因此省略沒畫泥兰。從上圖可以發(fā)現(xiàn),BTree 的樹形狀相較于我們之前常見的二叉樹等結(jié)構(gòu)题禀,更為扁平和矮胖鞋诗。
之所以這樣設(shè)計,還是跟磁盤讀取的特點有關(guān)迈嘹。我們知道在建立索引時削彬,也是需要占據(jù)物理空間的全庸。而實際上當數(shù)據(jù)量比較大的時候,索引文件的大小也十分嚇人融痛『考慮到一個表上可能有多個索引、組合索引雁刷、數(shù)據(jù)行占用更小等情況覆劈,索引文件的大小可能達到物理盤中數(shù)據(jù)的1/10,甚至可達到1/3安券。
這就意味著索引無法全部裝入內(nèi)存之中墩崩。當通過索引對數(shù)據(jù)進行訪問時氓英,不可避免的需要對磁盤進行讀寫訪問侯勉。同時我們還知道,內(nèi)存的讀寫速度是磁盤的幾個數(shù)量級铝阐。因此在對索引結(jié)構(gòu)進行設(shè)計時要盡可能的減少對磁盤的讀寫次數(shù)址貌,也就是所謂的磁盤 I/O 次數(shù)。
這也就是索引會采用 BTree 這種扁平樹結(jié)構(gòu)的原因徘键,樹的層數(shù)越少练对,磁盤I/O的次數(shù)自然就越少。不僅如此吹害,我們上面提到過磁盤預讀的局部性原理螟凭。根據(jù)這個原理再加上頁表機制,能夠在進行磁盤讀取的時候更大化的提升性能它呀。
BTree 相較于其它的二叉樹結(jié)構(gòu)螺男,對磁盤的 I/O 次數(shù)已經(jīng)非常少了。但是在實際的數(shù)據(jù)庫應用中仍有些問題無法解決纵穿。
一是無法定位到數(shù)據(jù)行下隧。通過 BTree 可以根據(jù)主鍵的排序定位出主鍵的位置,但是由于數(shù)據(jù)表的記錄有多個字段谓媒,僅僅定位到主鍵是不夠淆院,還需要定位到數(shù)據(jù)行。雖然這個問題可以通過在 BTree 的節(jié)點中存儲數(shù)據(jù)行或者增加定位的字段句惯,但是這種方式會使得 BTree 的深度大幅度提高土辩,從而也導致 I/O 次數(shù)的提高。
二是無法處理范圍查詢抢野。在實際的應用中脯燃,數(shù)據(jù)庫范圍查詢的頻率非常高,而 BTree 只能定位到一個索引位置蒙保。雖然可以通過先后查詢范圍的左右界獲得辕棚,但是這樣的操作實際上無法很好的利用磁盤預讀的局部性原理,先后查詢可能會造成通過預讀取的物理地址離散,使得 I/O 的效率并不高逝嚎。
三是當數(shù)據(jù)量一大時扁瓢,BTree的高度依舊會變得很高,搜索效率還是會大幅度的下降补君。
問題總是推動改進的前提引几。基于以上的問題考慮挽铁,就出現(xiàn)了對 BTree 的優(yōu)化版本伟桅,也就是 B+Tree。
B+Tree索引
B+Tree 一看就是在 BTree 的基礎(chǔ)上做了改進叽掘,那么到底改變了什么呢楣铁。廢話不多說,先上圖更扁。
上圖實際上是一種帶有順序索引的 B+Tree盖腕,與最基本的 B+Tree 的區(qū)別就在于葉子節(jié)點是否通過指針相連。一般數(shù)據(jù)庫中常用的結(jié)構(gòu)都是這種帶有順序索引的 B+Tree浓镜。后文探討的也都是帶順序索引的 B+Tree 結(jié)構(gòu)溃列。
對比 BTree 和 B+Tree膛薛,我們可以發(fā)現(xiàn)二者主要在以下三個方面上的不同:
- 非葉子節(jié)點只存儲鍵值信息,不再存儲數(shù)據(jù)哄啄。
- 所有葉子節(jié)點之間都有一個鏈指針,指向下一個葉子節(jié)點增淹。
- 數(shù)據(jù)都存放在葉子節(jié)點中椿访。
看著 B+Tree虑润,像不像是一顆樹與一個有序鏈表的結(jié)合體。因而其實 B+Tree 也就是帶有樹和鏈表的部分優(yōu)勢拳喻。樹結(jié)構(gòu)使得有序檢索更為簡單哭当,I/O 次數(shù)更少;有序鏈表結(jié)構(gòu)使得可以按照鍵值排序的次序遍歷全部記錄冗澈。
B+Tree 在作為索引結(jié)構(gòu)時能夠帶來的好處有:
一,I/O 次數(shù)更少彻采。這是因為上文也說過,BTree 的節(jié)點是存放在內(nèi)存頁中的肛响。那么在相同的內(nèi)存頁大小(一般為4k)的情況下剃浇,B+Tree 能夠存儲更多的鍵值猎物,那么整體樹結(jié)構(gòu)的高度就會更小,需要的 I/O 次數(shù)也就越小淘讥。
二质帅,數(shù)據(jù)遍歷更為方便留攒。這個優(yōu)勢很明顯是由有序鏈表帶來的。通過葉子節(jié)點的鏈接魄揉,使得對所有數(shù)據(jù)的遍歷只需要在線性的鏈表上完成拭宁,這就非常適合區(qū)間檢索和范圍查詢杰标。
三兵怯,查詢性能更穩(wěn)定腔剂。這自然是由于只在葉子節(jié)點存儲數(shù)據(jù)掸犬,所以所有數(shù)據(jù)的查詢都會到達葉子節(jié)點,同時葉子節(jié)點的高度都相同湾碎,因此理論上來說所有數(shù)據(jù)的查詢速度都是一致的介褥。
正是由于 B+Tree 優(yōu)秀的結(jié)構(gòu)特性递惋,使得常用作索引的實現(xiàn)結(jié)構(gòu)溢陪。在 MySQL 中,存儲引擎 MyISAM 和 InnoDB 都分別以 B+Tree 實現(xiàn)了響應的索引設(shè)計贩挣。
物理存儲
雖說 B+Tree 結(jié)構(gòu)都可以用在 MyISAM 和 InnoDB没酣,但是這二者對索引的在物理存儲層次的實現(xiàn)方式卻不相同。InnoDB 實現(xiàn)的是聚簇索引绒净,而 MyISAM 實現(xiàn)的卻是非聚簇索引偿衰。在介紹聚簇索引之前下翎,我們需要先了解以下啥是佩奇,不對视事,是啥是「主鍵索引」和「輔助索引」。
其實概念很簡單跌穗。我們剛剛不是在講 B+Tree 的時候說過虏辫,樹的非葉子節(jié)點只存儲鍵值砌庄。沒錯就是這個鍵值,當這個鍵值是數(shù)據(jù)表的主鍵時鹤耍,那么所建立的就是主鍵索引稿黄;當這個鍵值是其它字段的時候,就是輔助索引族购。因而可以得出,主鍵索引只能有一個违施,而輔助索引卻可以有很多個瑟幕。
聚簇索引和非聚簇索引的區(qū)別也就是根據(jù)其對應的主鍵索引和輔助索引的不同特點而實現(xiàn)的。
聚簇索引
說回聚簇索引辣往。先丟個定義殖卑。
聚簇索引的主鍵索引的葉子結(jié)點存儲的是鍵值對應的數(shù)據(jù)本身;輔助索引的葉子結(jié)點存儲的是鍵值對應的數(shù)據(jù)的主鍵鍵值许起。
這句話的信息量挺大的菩鲜。首先睦袖,分析第一句話荣刑,主鍵索引的葉子節(jié)點存儲的是鍵值對應的數(shù)據(jù)本身。
我們知道董习,主鍵索引存儲的鍵值就是主鍵爱只。那么也就是說恬试,聚簇索引的主鍵索引,在葉子節(jié)點中存儲的是主鍵和主鍵對應的數(shù)據(jù)训柴。數(shù)據(jù)和主鍵索引是存儲在一起的幻馁,一起作為葉子節(jié)點的一部分越锈。
然后膘滨,分析第二句話火邓,輔助索引的葉子結(jié)點存儲的是鍵值對應的數(shù)據(jù)的主鍵鍵值。
我們又知道蹈矮,輔助索引存儲的鍵值是非主鍵的字段鸣驱。那就也就是說,通過輔助索引北滥,可以找到非主鍵字段對應的數(shù)據(jù)行中的主鍵闸翅。
重點來了坚冀。當然主鍵索引和輔助索引一結(jié)合,能干啥呢司训。當直接采用主鍵進行檢索時液南,可通過主鍵索引直接獲得數(shù)據(jù);而當采用非主鍵進行檢索時统扳,先需要通過輔助索引來獲得主鍵畅姊,然后再通過這個主鍵在主鍵索引中找到對應的數(shù)據(jù)行若未。
舉個例子吧。假設(shè)有這么一個數(shù)據(jù)表腕够。
那么采用聚簇索引的存儲方式,對應的主鍵索引為:(主鍵為ID)
對應的輔助索引為:(鍵值為Name玫荣,大概的意思):
所以當使用where ID = 7
這樣的條件查找主鍵捅厂,則按照B+樹的檢索算法即可查找到對應的葉節(jié)點资柔,之后獲得行數(shù)據(jù)贿堰。對Name列進行條件搜索,則需要兩個步驟:第一步在輔助索引B+樹中檢索Name故硅,到達其葉子節(jié)點獲取對應的主鍵纵搁。第二步使用主鍵在主鍵索引B+樹種再執(zhí)行一次B+樹檢索操作,最終到達葉子節(jié)點即可獲取整行數(shù)據(jù)徘层。
最后把以上過程整理總結(jié)一下利职,聚簇索引實際上的過程就分為以下兩個過程⊙垡現(xiàn)在這個圖應該能夠看懂了吧。
非聚簇索引
學完了聚簇索引,非聚簇索引就簡單多啦楞黄。同樣抡驼,先上定義。
非聚簇索引的主鍵索引和輔助索引幾乎是一樣的碎税,只是主索引不允許重復,不允許空值伟端,他們的葉子結(jié)點都存儲指向鍵值對應的數(shù)據(jù)的物理地址匪煌。
與聚簇索引來對比著看萎庭,上面的定義能夠說明什么呢。首先肴敛,主鍵索引和輔助索引的葉子結(jié)點都存儲著鍵值對應的數(shù)據(jù)的物理地址吗购,這說明無論是主鍵索引還是輔助索引都能夠通過直接獲得數(shù)據(jù),而不需要像聚簇索引那樣在檢索輔助索引時還得多繞一圈昨登。
同時還說明一個點贯底,葉子結(jié)點存儲的是物理地址禽捆,那么表示數(shù)據(jù)實際上是存在另一個地方的,并不是存儲在B+樹的結(jié)點中琐凭。這說明非聚簇索引的數(shù)據(jù)表和索引表是分開存儲的浊服。
同樣牙躺,對非聚簇索引的檢索過程來個總結(jié)。
無論是主鍵索引還是輔助索引的檢索過程吨掌,都只需要通過相應的 B+Tree 進行搜索即可獲得數(shù)據(jù)對應的物理地址,然后經(jīng)過依次磁盤 I/O 就可訪問數(shù)據(jù)窿侈。
對比聚簇索引和非聚簇索引史简,可以發(fā)現(xiàn)二者最主要的區(qū)別就是在于是否在 B+Tree 的節(jié)點中存儲數(shù)據(jù)学辱,也就是數(shù)據(jù)和索引是否存儲在一起。這個區(qū)別導致最大的問題就是聚簇索引的索引的順序和數(shù)據(jù)本身的順序是相同的衙傀,而非聚簇索引的順序跟數(shù)據(jù)的順序沒有啥關(guān)系萨咕。
索引優(yōu)化
介紹了這么多的索引危队,其實最終都是為了建立高性能的索引策略,對數(shù)據(jù)庫中的索引進行優(yōu)化金麸。索引的優(yōu)化有很多角度簿盅,針對特定的業(yè)務場景可采用不同的優(yōu)化策略桨醋。這里考慮到文章篇幅,就不具體介紹喜最,下次再出一篇專門講索引優(yōu)化的文章。簡單列舉一下在進行優(yōu)化時可以考慮的幾個方向:
1 獨立的列迷雪。索引列不能是表達式的一部分遂鹊,也不能是函數(shù)的參數(shù)。
2 前綴索引和索引選擇性。這二者實際上是相互制約的關(guān)系舟陆,制約條件在于前綴的長度。一般應選擇足夠長的前綴以保證較高的選擇性(保證查詢效率)忆谓,同時又不能太長以便節(jié)省空間踱承。
3 盡量使用覆蓋索引茎活。覆蓋索引是指一個索引包含所有需要查詢的字段的值,這樣在查詢時只需要掃描索引而無須再去讀取數(shù)據(jù)行盾饮,從而極大的提高性能懒熙。
4 使用索引掃描來做排序工扎。要知道,掃描索引本身是很快的呈础,在設(shè)計索引時丛忆,可盡可能的使用同一個索引既滿足排序蹬刷,又滿足查找行數(shù)據(jù)。
......
最后官疲,在建立索引時給幾個小貼士:
1 優(yōu)先使用自增key作為主鍵
2 記住最左前綴匹配原則
3 索引列不能參與計算
4 選擇區(qū)分度高的列作索引
5 能擴展就不要新建索引
......
總結(jié)
索引的概念和原理是我們在了解和精通數(shù)據(jù)庫過程中無法逃避的重點亮隙,而事實上建立高性能的索引對實際的應用場景也具有重要意義。本文的目的依舊是由淺入深的介紹索引這么個東西维费,從概念到實現(xiàn)再到最終的優(yōu)化策略犀盟。點到為止,學無止境阅畴。掌握了基本的理論和概念后,還需要在實際的服務器開發(fā)場景中針對具體的問題和服務進行索引優(yōu)化方案的設(shè)計和使用监署。
Reference
高性能 MySQL纽哥,Baron Schwartz 等人著,電子工業(yè)出版社
http://www.reibang.com/p/9e9aca844c13
https://www.runoob.com/mysql/mysql-index.html
https://www.cnblogs.com/Aiapple/p/5693239.html