「Mysql索引原理(二)」Mysql高性能索引實(shí)踐,索引概念、BTree索引、B+Tree索引

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è)鍵。

  1. 所有葉子都出現(xiàn)在同一水平,沒有任何信息(高度一致)。

    什么是階?

image.png

? ? ? ?節(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】

合并后

image.png

再次合并

image.png

總結(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)中。

image.png

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ù)最左前綴的查找。

  1. 全值匹配
    和索引中所有的列進(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+樹的核心:“有序鏈表且按列順序排列”

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。