1. 索引是什么
2. 索引的類型
3. BTree索引
? ? ? ?概念
? ? ? ?舉例:以5階數(shù)為列
4. B+Tree索引
? ? ? ?概念
? ? ? ?5階B+Tree插入舉例
? ? ? ?B+樹的優(yōu)點(diǎn)
? ? ? ?可以使用B+樹索引的查詢類型
? ? ? ?B+Tree索引的限制
索引是什么
? ? ? ?索引是存儲(chǔ)引擎用于快速找到記錄的一種數(shù)據(jù)結(jié)構(gòu)。存儲(chǔ)引擎首先在索引中找到對(duì)應(yīng)值,然后根據(jù)匹配的索引記錄找到對(duì)應(yīng)的數(shù)據(jù)行。比如,
? ? ? ?select first_name from actor where actor.id=5;
? ? ? ?mysql先在索引上按值進(jìn)行查找,然后返回所有包含該值的數(shù)據(jù)行。
索引的類型
? ? ? ?并沒有統(tǒng)一的索引標(biāo)準(zhǔn),不同存儲(chǔ)引擎的索引的工作方式并不一樣,也不是所有的存儲(chǔ)引擎都支持所有類型的索引。即使多個(gè)存儲(chǔ)引擎支持同一種類型的索引,其底層的實(shí)現(xiàn)也不一樣。
? ? ? ?mysql中常用的索引類型包括BTree索引、B+Tree索引、哈希索引。在介紹索引的使用和索引的優(yōu)點(diǎn)之前,需要先弄清楚索引抱哈的。
BTree索引
概念
? ? ? ?B樹是一種自平衡樹數(shù)據(jù)結(jié)構(gòu),它維護(hù)有序數(shù)據(jù)并允許以對(duì)數(shù)時(shí)間進(jìn)行搜索,順序訪問,插入和刪除。B樹是二叉搜索樹的一般化,因?yàn)楣?jié)點(diǎn)可以有兩個(gè)以上的子節(jié)點(diǎn)。與其他自平衡二進(jìn)制搜索樹不同,B樹非常適合讀取和寫入相對(duì)較大的數(shù)據(jù)塊(如光盤)的存儲(chǔ)系統(tǒng)。它通常用于數(shù)據(jù)庫(kù)和文件系統(tǒng)。
定義:
B樹是一種平衡的多分樹,通常我們說m階的B樹,它必須滿足如下條件:
? ? ? ?1. 每個(gè)節(jié)點(diǎn)最多只有m個(gè)子節(jié)點(diǎn)。
? ? ? ?2. 每個(gè)非葉子節(jié)點(diǎn)(除了根)具有至少? m/2?子節(jié)點(diǎn)。
? ? ? ?3. 如果根不是葉節(jié)點(diǎn),則根至少有兩個(gè)子節(jié)點(diǎn)。
? ? ? ?4. 具有k個(gè)子節(jié)點(diǎn)的非葉節(jié)點(diǎn)包含k -1個(gè)鍵。
-
所有葉子都出現(xiàn)在同一水平,沒有任何信息(高度一致)。
什么是階?
? ? ? ?節(jié)點(diǎn)【13,16,19】擁有的子節(jié)點(diǎn)數(shù)目最多,有四個(gè)子節(jié)點(diǎn)(粉色節(jié)點(diǎn)),所以可以定義上面的圖片為4階B樹。
什么是根節(jié)點(diǎn)?
? ? ? ?節(jié)點(diǎn)【10】即為根節(jié)點(diǎn)。包含子節(jié)點(diǎn)數(shù)關(guān)系式2<= M <=m,M為子節(jié)點(diǎn)數(shù)量;包含的元素?cái)?shù)量 1<= K <=m-1,K為元素?cái)?shù)量。
什么是內(nèi)部節(jié)點(diǎn)?
? ? ? ?節(jié)點(diǎn)【13,16,19】、節(jié)點(diǎn)【3,6】都為內(nèi)部節(jié)點(diǎn),特征:內(nèi)部節(jié)點(diǎn)是除葉子節(jié)點(diǎn)和根節(jié)點(diǎn)之外的所有節(jié)點(diǎn),擁有父節(jié)點(diǎn)和子節(jié)點(diǎn)。包含子節(jié)點(diǎn)數(shù)關(guān)系式符合(m/2)<= M <=m關(guān)系式,包含元素?cái)?shù)量M-1;包含的元素?cái)?shù)量 (m/2)-1<= K <=m-1,K為元素?cái)?shù)量。m/2向上取整。
什么是葉子節(jié)點(diǎn)?
? ? ? ?節(jié)點(diǎn)【1,2】、節(jié)點(diǎn)【11,12】等最后一層都為葉子節(jié)點(diǎn),葉子節(jié)點(diǎn)對(duì)元素的數(shù)量有相同的限制,但是沒有
子節(jié)點(diǎn),也沒有指向子節(jié)點(diǎn)的指針。葉子節(jié)點(diǎn)的元素符合(m/2)-1<= K <=m-1。
舉例:以5階數(shù)為列
插入操作
? ? ? ?規(guī)則:
若該節(jié)點(diǎn)元素個(gè)數(shù)小于m-1,直接插入;
若該節(jié)點(diǎn)元素個(gè)數(shù)等于m-1,引起節(jié)點(diǎn)分裂;以該節(jié)點(diǎn)中間元素為分界,取中間元素(偶數(shù)個(gè)數(shù),中間兩個(gè)隨機(jī)選取)插入到父節(jié)點(diǎn)中;
-
重復(fù)上面動(dòng)作,直到所有節(jié)點(diǎn)符合B樹的規(guī)則;最壞的情況一直分裂到根節(jié)點(diǎn),生成新的根節(jié)點(diǎn),高度增加1;
關(guān)鍵點(diǎn):
2<=根節(jié)點(diǎn)子節(jié)點(diǎn)個(gè)數(shù)<=5
3<=內(nèi)節(jié)點(diǎn)子節(jié)點(diǎn)個(gè)數(shù)<=5
1<=根節(jié)點(diǎn)元素個(gè)數(shù)<=4
2<=非根節(jié)點(diǎn)元素個(gè)數(shù)<=4
插入8
此時(shí)根節(jié)點(diǎn)元素個(gè)數(shù)為5,不符合 1<=根節(jié)點(diǎn)元素個(gè)數(shù)<=4,進(jìn)行分裂
接著插入元素【5】,【11】,【17】時(shí),不需要任何分裂操作
插入元素【13】
節(jié)點(diǎn)元素超出最大數(shù)量,進(jìn)行分裂,提取中間元素【13】,插入到父節(jié)點(diǎn)當(dāng)中
接著插入元素【6】,【12】,【20】,【23】時(shí),不需要任何分裂操作
插入【26】時(shí)
最右的葉子結(jié)點(diǎn)空間滿了,需要進(jìn)行分裂操作,中間元素【20】上移到父節(jié)點(diǎn)中
插入【4】時(shí)
導(dǎo)致最左邊的葉子結(jié)點(diǎn)被分裂,【4】恰好也是中間元素,上移到父節(jié)點(diǎn)中
【16】,【18】,【24】,【25】陸續(xù)插入不需要任何分裂操作
關(guān)鍵,插入【19】時(shí)
分裂
繼續(xù)分裂
此時(shí)樹的高度增加1。
刪除操作
? ? ? ?首先查找B樹中需刪除的元素,如果該元素在B樹中存在,則將該元素在其結(jié)點(diǎn)中進(jìn)行刪除;刪除該元素后,首先判斷該元素是否有左右孩子結(jié)點(diǎn),如果有,則上移孩子結(jié)點(diǎn)中的某相近元素(“左孩子最右邊的節(jié)點(diǎn)”或“右孩子最左邊的節(jié)點(diǎn)”)到父節(jié)點(diǎn)中,然后是移動(dòng)之后的情況;如果沒有,直接刪除。
某結(jié)點(diǎn)中元素?cái)?shù)目小于(m/2)-1,(m/2)向上取整,則需要看其某相鄰兄弟結(jié)點(diǎn)是否豐滿;
如果豐滿(結(jié)點(diǎn)中元素個(gè)數(shù)大于(m/2)-1),則向父節(jié)點(diǎn)借一個(gè)元素來滿足條件;
如果其相鄰兄弟都不豐滿,即其結(jié)點(diǎn)數(shù)目等于(m/2)-1,則該結(jié)點(diǎn)與其相鄰的某一兄弟結(jié)點(diǎn)進(jìn)行“合并”成一個(gè)結(jié)點(diǎn);
刪除【8】
刪除【20】
因?yàn)榉歉?jié)點(diǎn)元素個(gè)數(shù)>=2,只有一個(gè)元素【17】的節(jié)點(diǎn)不符合規(guī)定,因此需要將元素【23】上移動(dòng)。
刪除【18】
向父結(jié)點(diǎn)借一個(gè)元素,然后將最豐滿的相鄰兄弟結(jié)點(diǎn)中上移最后或最前一個(gè)元素到父節(jié)點(diǎn)中
最后一步刪除【5】
合并后
再次合并
總結(jié),增加導(dǎo)致分裂,刪除導(dǎo)致合并。
注意,BTree索引每個(gè)節(jié)點(diǎn)不但保存索引信息,還保存了對(duì)應(yīng)的數(shù)據(jù)行信息,找到一個(gè)節(jié)點(diǎn)相當(dāng)于找到了數(shù)據(jù)表中的一行。
B+Tree索引
概念
? ? ? ?B+Tree是BTree的一個(gè)變種,最大的區(qū)別是B+Tree內(nèi)部節(jié)點(diǎn)不保存數(shù)據(jù),只保存索引信息,所有數(shù)據(jù)都保存在葉子節(jié)點(diǎn),具有如下特征:
每個(gè)元素不保存數(shù)據(jù),只用來索引,所有數(shù)據(jù)都保存在葉子節(jié)點(diǎn)。
所有的葉子結(jié)點(diǎn)中包含了全部元素的信息,及指向含這些元素記錄的指針,且葉子結(jié)點(diǎn)本身依關(guān)鍵字的大小自小而大順序鏈接。
所有的中間節(jié)點(diǎn)元素都同時(shí)存在于子節(jié)點(diǎn),在子節(jié)點(diǎn)元素中是最大(或最小)元素
不但節(jié)點(diǎn)之間含有重復(fù)元素,而且葉子結(jié)點(diǎn)還用指針連接在一起。
? ? ? ?在上面這課樹中,根節(jié)點(diǎn)元素8是子節(jié)點(diǎn)2,5,8的最大元素,也是葉子節(jié)點(diǎn)6,8的最大元素。需要注意的是根節(jié)點(diǎn)的最大元素(這里是15),也就等同于整個(gè)B+樹的最大元素。以后無論插入刪除多少元素,始終保持最大元素在根節(jié)點(diǎn)當(dāng)中。
? ? ? ?至于葉子節(jié)點(diǎn),由于父節(jié)點(diǎn)的元素都出現(xiàn)在子節(jié)點(diǎn),因此葉子結(jié)點(diǎn)包含了全部元素的信息。并且每個(gè)葉子節(jié)點(diǎn)都帶有指向下一個(gè)節(jié)點(diǎn)的指針,形成了一個(gè)有序鏈表。
對(duì)于B+樹,只需記住葉子節(jié)點(diǎn)是個(gè)有序列表且包含全部元素?cái)?shù)據(jù)信息即可,影響到后續(xù)索引的使用。
5階B+Tree插入舉例
空樹插入【5】
一次插入【8】、【10】、【15】
插入【16】
? ? ? ?元素個(gè)數(shù)超過限制,進(jìn)行分裂,分裂規(guī)則同BTree,但是注意,分裂的元素保留在原節(jié)點(diǎn)中,同時(shí)葉子節(jié)點(diǎn)通過指針連接。
插入【17】、【18】
元素個(gè)數(shù)超過限制,進(jìn)行分裂,分裂的元素保留在原節(jié)點(diǎn)中。
B+樹的優(yōu)點(diǎn)
-
單元素查詢
在單元素查詢的時(shí)候,B+樹會(huì)自頂向下逐層查找節(jié)點(diǎn),最終找到匹配的葉子節(jié)點(diǎn)。
比如我們查找元素4
第一次磁盤IO
第二次磁盤IO
第三次磁盤IO
? ? ? ?B+樹的中間節(jié)點(diǎn)只保存索引信息,不保存元素其他相關(guān)信息,所以同樣大小的磁盤頁(yè)可以容納更多的節(jié)點(diǎn)元素,這就意味著在數(shù)據(jù)量相同的情況下,B+樹更加的矮胖,因此IO的次數(shù)也就較少。B+樹查詢必須查找到葉子節(jié)點(diǎn),每一次查找都是穩(wěn)定的;
- B樹的范圍查找及過程與B+樹對(duì)比
比如,查找范圍3~11
B樹,首先自頂向下,查找到范圍的下限(3)
中序遍歷到元素6
中序遍歷到元素8
中序遍歷到元素9
中序遍歷到元素11
B+樹,自頂向下,查找到范圍的下限(3)
通過鏈表指針遍歷
總結(jié),B+樹比B樹更適合做數(shù)據(jù)庫(kù)索引:
1)B+樹的磁盤讀寫代價(jià)更低
? ? ? ?B+樹的內(nèi)部結(jié)點(diǎn)并沒有指向關(guān)鍵字具體信息的指針。因此其內(nèi)部結(jié)點(diǎn)相對(duì)B 樹更小。如果把所有同一內(nèi)部結(jié)點(diǎn)的關(guān)鍵字存放在同一盤塊中,那么盤塊所能容納的關(guān)鍵字?jǐn)?shù)量也越多。一次性讀入內(nèi)存中的需要查找的關(guān)鍵字也就越多。相對(duì)來說IO讀寫次數(shù)也就降低了;
2)B+樹查詢效率更加穩(wěn)定
? ? ? ?由于非終結(jié)點(diǎn)并不是最終指向文件內(nèi)容的結(jié)點(diǎn),而只是葉子結(jié)點(diǎn)中關(guān)鍵字的索引。所以任何關(guān)鍵字的查找必須走一條從根結(jié)點(diǎn)到葉子結(jié)點(diǎn)的路。所有關(guān)鍵字查詢的路徑長(zhǎng)度相同,導(dǎo)致每一個(gè)數(shù)據(jù)的查詢效率相當(dāng);
3)B+樹便于范圍查詢(最重要的原因,范圍查找是數(shù)據(jù)庫(kù)的常態(tài))
? ? ? ?B樹在提高了IO性能的同時(shí)并沒有解決元素遍歷的我效率低下的問題,正是為了解決這個(gè)問題,B+樹應(yīng)用而生。B+樹只需要去遍歷葉子節(jié)點(diǎn)就可以實(shí)現(xiàn)整棵樹的遍歷。而且在數(shù)據(jù)庫(kù)中基于范圍的查詢是非常頻繁的,而B樹不支持這樣的操作或者說效率太低;
? ? ? ?Mysql數(shù)據(jù)庫(kù)中,大多數(shù)存儲(chǔ)引擎都使用這種索引,存儲(chǔ)引擎以不同的方式使用B+Tree索引,性能也各不相同,各有優(yōu)劣,如,MyISAM使用前綴壓縮技術(shù)使得索引更小,但I(xiàn)nnoDB按照原數(shù)據(jù)格式進(jìn)行存儲(chǔ)。再如MyISAM索引通過數(shù)據(jù)的物理位置引用被索引的行,而InnoDB通過主鍵引用被索引的行。
? ? ? ?后面這句話什么意思呢?MyISAM索引文件和數(shù)據(jù)文件是分離的,索引文件僅保存記錄所在頁(yè)的指針,通過這些指針指向的物理地址來讀取頁(yè),進(jìn)而讀取索引的行。
? ? ? ?InnoDB存儲(chǔ)引擎采用“聚集“索引的數(shù)據(jù)存儲(chǔ)方式實(shí)現(xiàn),所謂聚集,就是指數(shù)據(jù)行和相鄰的鍵值緊湊的存儲(chǔ)在一起。在InnoDB中,表數(shù)據(jù)本身就是按B+Tree組織的一個(gè)索引結(jié)構(gòu),這棵樹的葉節(jié)點(diǎn)data域完整的保存了數(shù)據(jù)記錄。
可以使用B+樹索引的查詢類型
? ? ? ?B+樹索引能夠加快訪問數(shù)據(jù)的速度,因?yàn)榇鎯?chǔ)引擎不再需要進(jìn)行全表掃描來獲取需要的數(shù)據(jù),取而代之的是從索引的根節(jié)點(diǎn)開始進(jìn)行搜索。根節(jié)點(diǎn)的槽中存放了指向子節(jié)點(diǎn)的指針,存儲(chǔ)引擎根據(jù)這些指針向下層查找。B+樹對(duì)索引列hi順序組織數(shù)據(jù)的,所以很適合查找范圍數(shù)據(jù),其實(shí)工作中大部分查詢語(yǔ)句都是范圍查找。
例如,創(chuàng)建表:
CREATE TABLE `people` (
`last_name` varchar(50) CHARACTER SET utf8 NOT NULL,
`first_name` varchar(50) CHARACTER SET utf8 NOT NULL,
`dob` date NOT NULL,
`gender` enum('m','f') CHARACTER SET utf8 NOT NULL,
KEY `myindex` (`last_name`,`first_name`,`dob`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=latin1 ROW_FORMAT=COMPACT;
數(shù)據(jù)的B+Tree排列方式:
索引排列順序依據(jù)索引創(chuàng)建時(shí)列的順序。
? ? ? ?并不是所有的查詢都能使用到B+樹索引,B+樹索引適用于全鍵值、鍵值范圍或鍵前綴查找等,其中鍵前綴查找只適合用于根據(jù)最左前綴的查找。
- 全值匹配
和索引中所有的列進(jìn)行匹配,例如查詢姓名是Cuba Allen、出生于1960-01-01的人。
SELECT * FROM `people` where last_name='Allen' and first_name="Cuba" and dob="1960-01-01"
2.匹配最左前綴
查找所有姓為Allen的人,即只使用索引的第一列。
SELECT * FROM `people` where last_name='Allen'
3.匹配列前綴
查找所有姓以A開頭的人。即只使用索引的第一列
SELECT * FROM `people` where last_name like 'B%'
4.匹配范圍值
查找姓在Allen和Barrymore之間的人。
SELECT * FROM `people` where last_name >= 'Allen' and last_name <='Barrymore'
5.精確匹配某一列并范圍匹配另一列
查找姓為Allen,并且名字是字母K開頭的人。即第一列l(wèi)ast_name全匹配,第二列first_name范圍匹配
select * from people where last_name='Allen' and first_name like 'K%'
6.只訪問索引的查詢
? ? ? ?查詢只需要訪問索引,而無需訪問數(shù)據(jù)行,后面會(huì)單獨(dú)討論這種覆蓋索引的優(yōu)化。
? ? ? ?另,索引節(jié)點(diǎn)是有序鏈表,索引除了按值查找外,還可以用于查詢中的order by 操作,即按順序查找,前提是Order by 滿足上述幾種查詢類型。
? ? ? ?如:
select * from people where last_name='Allen' order by last_name
B+Tree索引的限制
- 如果不是按照索引的最左列開始查找,則無法使用索引。
例如上述例子,索引無法用于查找名字為Bill的人,也無法用于查找某個(gè)特定生日的人。
- 如果查詢中有某個(gè)列的范圍查詢,則右邊所有列都無法使用索引優(yōu)化查詢。
select * from people where last_name='Allen' and first_name like 'J%' and dob='1976-12-23'
這個(gè)查詢只能使用索引的前兩列,原因是后面列的排序是建立在前列完全一致的基礎(chǔ)上的。
-
不能跳過索引中的列
如,上述索引無法用于查找姓為Allen且出生日期是1960-01-01的人。如果不指出第二列first_name,那么mysql只能會(huì)用索引的第一列。
select * from people where last_name = 'Allen' and dob='1960-01-01'
總結(jié):索引列的順序太重要了,牢記B+樹的核心:“有序鏈表且按列順序排列”。