【數據庫】:爲什麼MySQL數據庫索引選擇使用B+樹

引言

在進一步分析爲什麼MySQL數據庫索引選擇使用B+樹之前,我相信很多小夥伴對數據結構中的樹還是有些許模糊的,因此我們由淺入深一步步探討樹的演進過程,在一步步引出B樹以及爲什麼MySQL數據庫索引選擇使用B+樹!

學過數據結構的一般對最基礎的樹都有所認識,因此我們就從與我們主題更爲相近的二叉查找樹開始。

1. 二叉查找樹

1.1. 二叉樹簡介

二叉查找樹也稱爲有序二叉查找樹,滿足二叉查找樹的一般性質,是指一棵空樹具有如下性質:

1、任意節點左子樹不爲空,則左子樹的值均小於根節點的值;

2、任意節點右子樹不爲空,則右子樹的值均大於於根節點的值;

3、任意節點的左右子樹也分別是二叉查找樹;

4、沒有鍵值相等的節點;
在這裏插入圖片描述

上圖爲一個普通的二叉查找樹,按照中序遍歷的方式可以從小到大的順序排序輸出:2、3、5、6、7、8。

對上述二叉樹進行查找,如查鍵值爲5的記錄,先找到根,其鍵值是6,6大於5,因此查找6的左子樹,找到3;而5大於3,再找其右子樹;一共找了3次。如果按2、3、5、6、7、8的順序來找同樣需求3次。用同樣的方法在查找鍵值爲8的這個記錄,這次用了3次查找,而順序查找需要6次。計算平均查找次數得:順序查找的平均查找次數爲(1+2+3+4+5+6)/ 6 = 3.3次,二叉查找樹的平均查找次數爲(3+3+3+2+2+1)/6=2.3次。二叉查找樹的平均查找速度比順序查找來得更快

1.2. 侷限性及應用

一個二叉查找樹是由n個節點隨機構成,所以,對於某些情況,二叉查找樹會退化成一個有n個節點的線性鏈。如下圖:
在這裏插入圖片描述

大家看上圖,如果我們的根節點選擇是最小或者最大的數,那麼二叉查找樹就完全退化成了線性結構。上圖中的平均查找次數爲(1+2+3+4+5+5)/6=3.16次,和順序查找差不多。顯然這個二叉樹的查詢效率就很低,因此若想最大性能的構造一個二叉查找樹,需要這個二叉樹是平衡的(這裏的平衡從一個顯著的特點可以看出這一棵樹的高度比上一個輸的高度要大,在相同節點的情況下也就是不平衡),從而引出了一個新的定義-平衡二叉樹AVL。

2. AVL樹

2.1. AVL樹簡介

AVL樹是帶有平衡條件的二叉查找樹,一般是用平衡因子差值判斷是否平衡並通過旋轉來實現平衡,左右子樹樹高差不超過1,和紅黑樹相比,它是嚴格的平衡二叉樹,平衡條件必須滿足(所有節點的左右子樹高度差不超過1)。不管我們是執行插入還是刪除操作,只要不滿足上面的條件,就要通過旋轉來保持平衡。而旋轉是非常耗時的,由此我們可以知道AVL樹適合用於插入刪除次數比較少,但查找多的情況。

在這裏插入圖片描述

從上面是一個普通的平衡二叉樹,這張圖我們可以看出,任意節點的左右子樹的平衡因子差值都不會大於1。

2.2. 侷限性

由於維護這種高度平衡所付出的代價比從中獲得的效率收益還大,故而實際的應用不多,更多的地方是用追求局部而不是非常嚴格整體平衡的紅黑樹。當然,如果應用場景中對插入刪除不頻繁,只是對查找要求較高,那麼AVL還是較優於紅黑樹。

2.3. 應用

1、Windows NT內核中廣泛存在;

3. 紅黑樹

3.1. 紅黑樹簡介

一種二叉查找樹,但在每個節點增加一個存儲位表示節點的顏色,可以是red或black。通過對任何一條從根到葉子的路徑上各個節點着色的方式的限制,紅黑樹確保沒有一條路徑會比其它路徑長出兩倍。它是一種弱平衡二叉樹(由於是弱平衡,可以推出,相同的節點情況下,AVL樹的高度低於紅黑樹)。相對於要求嚴格的AVL樹來說,它的旋轉次數變少,所以對於搜索、插入、刪除操作多的情況下,我們就用紅黑樹。

3.2. 性質

1、每個節點非紅即黑;
2、根節點是黑的;
3、每個葉節點(葉節點即樹尾端NULL指針或NULL節點)都是黑的;
4、如果一個節點是紅的,那麼它的兩兒子都是黑的;
5、對於任意節點而言,其到葉子點樹NULL指針的每條路徑都包含相同數目的黑節點;
6、每條路徑都包含相同的黑節點;

在這裏插入圖片描述

3.3. 應用

1、廣泛用於C++的STL中,Map和Set都是用紅黑樹實現的;
2、著名的Linux進程調度Completely Fair Scheduler,用紅黑樹管理進程控制塊,進程的虛擬內存區域都存儲在一顆紅黑樹上,每個虛擬地址區域都對應紅黑樹的一個節點,左指針指向相鄰的地址虛擬存儲區域,右指針指向相鄰的高地址虛擬地址空間;
3、IO多路複用epoll的實現採用紅黑樹組織管理sockfd,以支持快速的增刪改查;
4、Nginx中用紅黑樹管理timer,因爲紅黑樹是有序的,可以很快的得到距離當前最小的定時器;
5、Java中TreeMap的實現;

4. B/B+樹

說了上述的三種樹:二叉查找樹、AVL和紅黑樹,似乎我們還沒有摸到MySQL爲什麼要使用B+樹作爲索引的實現,不要急,接下來我們就先探討一下什麼是B樹。

4.1. B+樹簡介

我們在MySQL中的數據一般是放在磁盤中的,讀取數據的時候肯定會有訪問磁盤的操作,磁盤中有兩個機械運動的部分,分別是盤片旋轉和磁臂移動。盤片旋轉就是我們市面上所提到的多少轉每分鐘,而磁盤移動則是在盤片旋轉到指定位置以後,移動磁臂後開始進行數據的讀寫。那麼這就存在一個定位到磁盤中的塊的過程,而定位是磁盤的存取中花費時間比較大的一塊,畢竟機械運動花費的時候要遠遠大於電子運動的時間。當大規模數據存儲到磁盤中的時候,顯然定位是一個非常花費時間的過程,但是我們可以通過B樹進行優化,提高磁盤讀取時定位的效率。

爲什麼B類樹可以進行優化呢?我們可以根據B類樹的特點,構造一個多階的B類樹,然後在儘量多的在結點上存儲相關的信息,保證層數儘量的少,以便後面我們可以更快的找到信息,磁盤的I/O操作也少一些,而且B類樹是平衡樹,每個結點到葉子結點的高度都是相同,這也保證了每個查詢是穩定的。

總的來說,B/B+樹是爲了磁盤或其它存儲設備而設計的一種平衡多路查找樹(相對於二叉,B樹每個內節點有多個分支),與紅黑樹相比,在相同的的節點的情況下,一顆B/B+樹的高度遠遠小於紅黑樹的高度(在下面B/B+樹的性能分析中會提到)。B/B+樹上操作的時間通常由存取磁盤的時間和CPU計算時間這兩部分構成,而CPU的速度非常快,所以B樹的操作效率取決於訪問磁盤的次數,關鍵字總數相同的情況下B樹的高度越小,磁盤I/O所花的時間越少

4.2. B樹的性質

1、定義任意非葉子結點最多隻有M個兒子,且M>2;
2、根結點的兒子數爲[2, M];
3、除根結點以外的非葉子結點的兒子數爲[M/2, M];
4、每個結點存放至少M/2-1(取上整)和至多M-1個關鍵字;(至少2個關鍵字)
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、所有葉子結點位於同一層;

在這裏插入圖片描述

這裏只是一個簡單的B樹,在實際中B樹節點中關鍵字很多的,上面的圖中比如35節點,35代表一個key(索引),而小黑塊代表的是這個key所指向的內容在內存中實際的存儲位置,是一個指針。

5. B+樹

5.1. B+樹簡介

B+Tree是在B-Tree基礎上的一種優化,使其更適合實現外存儲索引結構,InnoDB存儲引擎就是用B+Tree實現其索引結構。

從上一節中的B-Tree結構圖中可以看到每個節點中不僅包含數據的key值,還有data值。而每一個頁的存儲空間是有限的,如果data數據較大時將會導致每個節點(即一個頁)能存儲的key的數量很小,當存儲的數據量很大時同樣會導致B-Tree的深度較大,增大查詢時的磁盤I/O次數,進而影響查詢效率。在B+Tree中,所有數據記錄節點都是按照鍵值大小順序存放在同一層的葉子節點上,而非葉子節點上只存儲key值信息,這樣可以大大加大每個節點存儲的key值數量,降低B+Tree的高度。

5.2. B+樹的性質

B+Tree相對於B-Tree有幾點不同:
1、非葉子節點只存儲鍵值信息。
2、所有葉子節點之間都有一個鏈指針。
3、數據記錄都存放在葉子節點中。
4、更適合於文件系統;

將上一節中的B-Tree優化,由於B+Tree的非葉子節點只存儲鍵值信息,假設每個磁盤塊能存儲4個鍵值及指針信息,則變成B+Tree後其結構如下圖所示:
在這裏插入圖片描述

通常在B+Tree上有兩個頭指針,一個指向根節點,另一個指向關鍵字最小的葉子節點,而且所有葉子節點(即數據節點)之間是一種鏈式環結構。因此可以對B+Tree進行兩種查找運算:一種是對於主鍵的範圍查找和分頁查找,另一種是從根節點開始,進行隨機查找。

可能上面例子中只有22條數據記錄,看不出B+Tree的優點,下面做一個推算:

InnoDB存儲引擎中頁的大小爲16KB,一般表的主鍵類型爲INT(佔用4個字節)或BIGINT(佔用8個字節),指針類型也一般爲4或8個字節,也就是說一個頁(B+Tree中的一個節點)中大概存儲16KB/(8B+8B)=1K個鍵值(因爲是估值,爲方便計算,這裏的K取值爲〖10〗^3)。也就是說一個深度爲3的B+Tree索引可以維護10^3 * 10^3 * 10^3 = 10億 條記錄。

實際情況中每個節點可能不能填充滿,因此在數據庫中,B+Tree的高度一般都在2~4層。mysql的InnoDB存儲引擎在設計時是將根節點常駐內存的,也就是說查找某一鍵值的行記錄時最多只需要1~3次磁盤I/O操作。

數據庫中的B+Tree索引可以分爲聚集索引(clustered index)和輔助索引(secondary index)。上面的B+Tree示例圖在數據庫中的實現即爲聚集索引,聚集索引的B+Tree中的葉子節點存放的是整張表的行記錄數據。輔助索引與聚集索引的區別在於輔助索引的葉子節點並不包含行記錄的全部數據,而是存儲相應行數據的聚集索引鍵,即主鍵。當通過輔助索引來查詢數據時,InnoDB存儲引擎會遍歷輔助索引找到主鍵,然後再通過主鍵在聚集索引中找到完整的行記錄數據。

5.3. 應用

1、B和B+樹主要用在文件系統以及數據庫做索引,比如MySQL;

6. B/B+樹性能分析

n個節點的平衡二叉樹的高度爲H(即logn),而n個節點的B/B+樹的高度爲logt((n+1)/2)+1;
若要作爲內存中的查找表,B樹卻不一定比平衡二叉樹好,尤其當m較大時更是如此。因爲查找操作CPU的時間在B-樹上是O(mlogtn)=O(lgn(m/lgt)),而m/lgt>1;所以m較大時O(mlogtn)比平衡二叉樹的操作時間大得多。因此在內存中使用B樹必須取較小的m。(通常取最小值m=3,此時B-樹中每個內部結點可以有2或3個孩子,這種3階的B-樹稱爲2-3樹)。

7. 爲什麼說B+樹比B樹更適合數據庫索引?

1、 B+樹的磁盤讀寫代價更低:B+樹的內部節點並沒有指向關鍵字具體信息的指針,因此其內部節點相對B樹更小,如果把所有同一內部節點的關鍵字存放在同一盤塊中,那麼盤塊所能容納的關鍵字數量也越多,一次性讀入內存的需要查找的關鍵字也就越多,相對IO讀寫次數就降低了。

2、B+樹的查詢效率更加穩定:由於非終結點並不是最終指向文件內容的結點,而只是葉子結點中關鍵字的索引。所以任何關鍵字的查找必須走一條從根結點到葉子結點的路。所有關鍵字查詢的路徑長度相同,導致每一個數據的查詢效率相當。

3、由於B+樹的數據都存儲在葉子結點中,分支結點均爲索引,方便掃庫,只需要掃一遍葉子結點即可,但是B樹因爲其分支結點同樣存儲着數據,我們要找到具體的數據,需要進行一次中序遍歷按序來掃,所以B+樹更加適合在區間查詢的情況,所以通常B+樹用於數據庫索引。

PS:我在知乎上看到有人是這樣說的,我感覺說的也挺有道理的:

他們認爲數據庫索引採用B+樹的主要原因是:B樹在提高了IO性能的同時並沒有解決元素遍歷效率低下的問題,正是爲了解決這個問題,B+樹應用而生。B+樹只需要去遍歷葉子節點就可以實現整棵樹的遍歷。而且在數據庫中基於範圍的查詢是非常頻繁的,而B樹不支持這樣的操作或者說效率太低。

原文:

  1. https://blog.csdn.net/bntx2jsqfehy7/article/details/79453193
  2. https://blog.csdn.net/ifollowrivers/article/details/73614549
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章