解析B+樹比B樹更加適合做數據庫索引的原因

前言

數據庫常用的索引有Hash索引、B+Tree索引 、全文索引。Hash索引雖然等值查詢會很快(單條記錄查詢),但其無法進行有效的範圍查詢,而範圍查詢是數據庫的常用操作,因此很多存儲引擎更傾向於使用B+Tree索引,例如MyISAM與InnoDB 都是默認使用B+Tree索引(它們的一個區別是InnoDB使用聚集索引,而MyISAM使用非聚集索引)。本文主要是討論B+樹與B樹結構差不多,爲什麼會用B+樹而不是B樹來做索引。
這裏是通過B+樹和B樹的數據結構角度來分析B+樹更適合做索引的原因。而我的另外一篇博文從底層出發,通過分析B+樹具體實現索引的原理來解析B+樹更適合做索引的原因-《從底層解析B+索引提高查詢速度的原因》

B樹

B樹是平衡的多路查找樹,M階B樹具有以下特點:
1.定義任意非葉子結點最多隻有M個兒子;且M>2;

2.根結點的兒子數爲[2, M];

3.除根結點以外的非葉子結點的兒子數爲[M/2, M];

4.每個結點存放至少(M/2)-1(取上整)和至多M-1個關鍵字;

5.非葉子結點的關鍵字個數=指向兒子的指針個數-1;

6.非葉子結點的關鍵字:K[1], K[2], …, K[M-1];且K[i] < K[i+1];(結點內的關鍵字有序)

7.非葉子結點的指針:P[1], P[2], …, P[M];其中P[1]指向關鍵字小於K[1]的子樹,P[M]指向關鍵字大於K[M-1]的子樹,其它P[i]指向關鍵字屬於(K[i-1], K[i])的子樹;(關鍵字左邊指針指向的子樹所有關鍵字值都小於該關鍵字,右邊指針指向的所有子樹關鍵值都大於該關鍵字)

8.所有葉子結點位於同一層

9.中序遍歷B樹會得到有序的關鍵字排列。
在這裏插入圖片描述

B+樹

B+樹是B樹的變體,也是一種多路搜索樹:其定義基本與B-樹同,除了以下幾點:
1.非葉子結點的子樹指針與關鍵字個數相同或者子樹指針數=關鍵字個數+1;

2.非葉結點僅具有索引下一層作用,不存儲數據的指針,跟記錄有關的信息均存放在葉結點中。

3.非葉子結點的子樹指針P[i],指向關鍵字值屬於[K[i], K[i+1])的子樹(B樹是開區間);

4.爲所有葉子結點增加一個鏈指針;樹的所有葉結點構成一個有序鏈表,可以按照關鍵碼排序的次序遍歷全部記錄。

5.所有關鍵字具體數據或者數據地址都在葉子結點。

三階B+樹(關鍵字數=孩子指針數)
在這裏插入圖片描述
四階B+樹(關鍵字數=孩子指針數-1)
在這裏插入圖片描述
注意:
B+樹有兩種實現方式,非葉子結點的子結點數=關鍵字數,另一種爲非葉結點的關鍵字數=子結點數-1,雖然他們數據排列結構不一樣,但其原理還是一樣的,Mysql 的B+樹是用第一種方式實現。

兩者皆可的原因是因爲B+樹的重點不在於關鍵字數和孩子指針數之間的關係,而是非葉子結點不存儲Data域,葉子結點才存儲Data域和葉子結點之間有指針連接,構成有序鏈表。

分析爲什麼B+樹更適合做索引

我們可以先看看B+樹在B樹結構之上的改進,B+樹在B樹的基礎之上最重要的改進就是非葉子結點只存儲關鍵字和下一層的索引,不存儲Data域,只在葉子結點存儲Data域,且爲所有葉子結點增加一個鏈指針,使所有葉結點構成一個有序鏈表,因此當我們需要有序遍歷所有關鍵字時,直接從最小關鍵字的葉子結點開始遍歷即可。
(注: Data域可以存放具體數據或者是數據的地址,區別在下面詳述)

1、非葉子結點不存儲Data域的好處
(1)非葉子結點不存儲Data域,只是起着目錄的作用,使得每一個非葉子結點可以存放更多的關鍵字和下一層結點的指針。數據庫是存儲在磁盤上的,我們讀取數據是從磁盤讀取到內存中,我們在進行磁盤預讀取時,是以塊(也可稱作數據頁)的單位進行數據讀取,我們在檢索B/B+樹的結點時,每次以塊爲單位將一個結點讀取到內存中,若一塊磁盤包含了樹結點以外的數據,就造成了浪費,因此我們需要使每一個結點的容量大小正好或者接近一塊磁盤(數據頁,一般是16k)的大小。於是我們在構建B+/B樹時,樹的階數其實就取決於一塊磁盤中能容納多少個關鍵字以及相關的索引和Data域。B+樹的非葉子結點不存儲Data域,因此它可以存儲更多個關鍵字和下一層結點的指針,因此B+樹會比B樹更矮胖。若我需要查找的關鍵字正好在葉子結點,因爲B+樹比B樹更矮,B+樹所進行的I/O次數更少,因爲途中經過每一層,我們都需要進行一次I/O讀取一個結點,B+樹會更矮,途徑的層數會更少。

(2)使得B+樹查詢速度更穩定,B+樹所有關鍵字數據地址都存在葉子節點上,所以每次查找的次數都相同所以查詢速度要比B樹更穩定;

2、所有葉結點構成一個有序鏈表的好處:
(1)B+樹便於區間查找(這點纔是B+樹作爲索引的關鍵),我們進行數據庫查詢大多爲區間查詢,B+樹天然具備排序功能,B+樹所有的葉子結點構成了一個有序鏈表,在查詢大小區間的數據時候更方便,B+樹查詢,只需通過頭結點往下找到第一個葉子結點,然後在葉子結點的鏈表上就行遍歷即可完成區間查詢。而且B+樹葉子結點是以索引字段大小順序進行排序的,索引字段大小相鄰的數據位置也相鄰,因此通過遍歷葉子結點鏈表只需要進行少量I/O讀取就能將所有數據都獲取。而B樹的索引字段大小相鄰近的結點可能隔得很遠,要想進行區間查詢需要不停的進行中序遍歷,相鄰的元素可能在內存中不相鄰,所以緩存命中性沒有B+樹好。

(2)B+樹全結點遍歷更快:B+樹遍歷整棵樹只需要遍歷所有的葉子節點即可,而不需要像B樹一樣需要進行中序遍歷,這有利於數據庫做全表掃描。

聚集索引和非聚集索引

1、聚集索引-數據行存儲的物理順序與聚集索引值的邏輯順序相同(查詢效率快的原因),一個表中只能擁有一個聚集索引,一般默認是主鍵。例如InnoDB中,使用的就是聚集索引,默認聚集索引建立在主鍵上,所以數據的物理存儲順序其實就是以主鍵值的大小順序進行排序存儲。例如數據表中已經插入主鍵值爲1、3、5、7、9的數據(存儲順序以主鍵值大小順序進行存儲),此時我要插入主鍵值爲4的數據記錄,此時磁盤上數據的存儲順序就變成了1、3、4、5、7、9(這也是聚集索引修改慢的原因,插入數據可能造成數據的移動,需要對數據頁進行重排序,開銷很大,所以一般採取主鍵自增)。

2、聚集索引的葉子結點就是對應的數據結點,也就是說葉子結點中存儲了關鍵字所在的一行的數據。(Innodb採用聚集索引,數據與索引聚集,Data域存的是數據本身。索引也是數據。數據和索引存在一個XX.IDB文件中,所以也叫聚集索引。)
在這裏插入圖片描述

3、非聚集索引中索引值的邏輯順序與磁盤上行的物理存儲順序不同,一個表中可以擁有多個非聚集索引。數據是以聚集索引值的順序進行存儲,但聚集索引只在搜索條件是主鍵的時候才起作用,因此我們常常使用到非聚集索引,那麼如何使用呢?我們需要以非聚集索引值建立一顆新的B+樹,比如主鍵爲C1,我們經常以字段C2爲條件進行查找,那可以在C2上建立一個非聚集索引,以C2值爲關鍵字建立一棵B+樹。當以C2爲條件進行查找時,就以C2值 在該B+樹上進查找,查找到後,若需要select的字段存在葉結點中,則返回該值,若不在,則需要進行回表操作。
(InnoDB回表操作-InnoDB中葉子結點除了存儲了該記錄的關鍵字,還存儲了主鍵值,通過主鍵值在聚集索引中查找到該具體記錄。)
(MyISAM回表操作-MyISAM中葉子結點除了存儲該記錄的關鍵字,還存儲了行號(物理地址),通過行號去查找具體記錄。)

4、非聚集索引的葉子層並不和實際數據頁相重疊,而採用葉子層包含一個指向表中的記錄在數據頁中的指針方式。非聚集索引層次多,不會造成數據重排序。( MyISAM使用非聚集索引,Data域中存的是數據地址。索引是索引,數據是數據。索引放在XX.MYI文件中,數據放在XX.MYD文件中,所以也叫非聚集索引。)
在這裏插入圖片描述

總結

結點內進行二分查找、整體表現爲多路搜索。

B+樹優點(選擇B+樹作爲索引的原因)

1、單一結點存儲更多的關鍵字,使得查詢的IO次數減少;

2、所有查詢都要查找到葉子節點,查詢性能穩定;

3、所有葉子節點形成有序鏈表,便於區間查詢以及全結點遍歷更快。

B樹的優點

如果經常訪問的數據離根節點很近,而B樹的非葉子節點本身存有關鍵字的具體數據或者其數據的地址,所以這種數據檢索的時候會要比B+樹快。有很多基於頻率的搜索是選用B樹,越頻繁query的結點越往根上走,前提是需要對query做統計,而且要對key做一些變化。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章