為什么mysql索引要使用B+樹,而不是B樹,紅黑樹
我們在MySQL中的數據一般是放在磁盤中的,讀取數據的時候肯定會有訪問磁盤的操作,磁盤中有兩個機械運動的部分,分別是盤片旋轉和磁臂移動。盤片旋轉就是我們市面上所提到的多少轉每分鍾,而磁盤移動則是在盤片旋轉到指定位置以后,移動磁臂后開始進行數據的讀寫。那么這就存在一個定位到磁盤中的塊的過程,而定位是磁盤的存取中花費時間比較大的一塊,畢竟機械運動花費的時候要遠遠大於電子運動的時間。當大規模數據存儲到磁盤中的時候,顯然定位是一個非常花費時間的過程,但是我們可以通過B樹進行優化,提高磁盤讀取時定位的效率。
為什么B類樹可以進行優化呢?我們可以根據B類樹的特點,構造一個多階的B類樹,然后在盡量多的在結點上存儲相關的信息,保證層數(樹的高度)盡量的少,以便后面我們可以更快的找到信息,磁盤的I/O操作也少一些,而且B類樹是平衡樹,每個結點到葉子結點的高度都是相同,這也保證了每個查詢是穩定的。
特別地:只有B-樹和B+樹,這里的B-樹是叫B樹,不是B減樹。沒有B減樹的。
以下摘自【程序員小灰】
什么是B樹
一個m階的B樹具有如下幾個特征:
1、根結點至少有兩個子女。 2、每個中間節點都包含k-1個元素和k個孩子,其中 m/2 <= k <= m 3、每一個葉子節點都包含k-1個元素,其中 m/2 <= k <= m 4、所有的葉子結點都位於同一層。5、每個節點中的元素從小到大排列,節點當中k-1個元素正好是k個孩子包含的元素的值域分划。
下面以3階B樹開始學習
這棵樹中,重點看(2,6)節點。該節點有兩個元素2和6,又有三個孩子1,(3,5),8。其中1小於元素2,(3,5)在(2,6)之間,8大於(3,5),正好符合上面所列的特征。
B樹查詢的流程:
比如上面的3階B樹查詢數值5。
第1次IO:
第2次IO:
第3次IO:
第3次內存比較:
總結:每次深度加1就會進行一次磁盤IO的查詢,將當前高度的數據加到內存中,再進行數值比較。從中可以看出相比大部分的查詢時間是花費在磁盤IO的速度上,所以要想提高性能就是將樹的高度足夠低,IO次數足夠少,這就是B樹的優勢。
B樹添加的流程:
比如樹里添加數值4 自頂向下查找4的節點位置,發現4應當插入到節點元素3,5之間
節點3,5已經是兩元素節點,無法再增加。父親節點 2, 6 也是兩元素節點,也無法再增加。根節點9是單元素節點,可以升級為兩元素節點。於是拆分節點3,5與節點2,6,讓根節點9升級為兩元素節點4,9。節點6獨立為根節點的第二個孩子。
從圖中可以看到,為了插入一個元素,幾乎全部的位置都變化了,這就是B樹的自平衡(始終維持多路平衡)。
B樹刪除的流程:
自頂向下查找元素11的節點位置。
刪除11后,節點12只有一個孩子,不符合B樹規范。因此找出12,13,15三個節點的中位數13,取代節點12,而節點12自身下移成為第一個孩子。(這個過程稱為左旋)
B樹應用
主要用於文件系統以及部分數據庫索引(MongoDB) 而Mysql是用B+樹的。
什么是B+樹
一個m階的B+樹具有如下幾個特征:
1、有k個子樹的中間節點包含有k個元素(B樹中是k-1個元素),每個元素不保存數據,只用來索引,所有數據都保存在葉子節點。 2、所有的葉子結點中包含了全部元素的信息,及指向含這些元素記錄的指針,且葉子結點本身依關鍵字的大小自小而大順序鏈接。 3、所有的中間節點元素都同時存在於子節點,在子節點元素中是最大(或最小)元素。
從上圖可以發現每一個父節點的元素都出現在子節點中,並且是子節點的最大(或最小)
從圖中可以看到根節點元素8是字節點2,5,8的最大元素,也是葉子節點6,8的最大元素。根節點15是子節點11,15的最大元素,也是葉子節點13,15的最大元素。
B+樹的最大元素始終位於根節點當中。所有葉子節點包含了全量元素信息,並且每一個葉子節點都帶有指向 下一個節點指針,形成了一個有序鏈表。
B+樹查詢流程
單個查詢:查詢某個元素3
第一次磁盤IO:
第二次磁盤IO:
第三次磁盤IO:
這樣看起來跟B樹沒有什么區別。但其實有兩點是需要注意的:
1、B+樹的中間節點沒有衛星數據的。所以同樣大小的磁盤頁可以容納更多的節點元素。(這就意味着B+會更加矮胖,查詢的IO次數會更少)
B樹的衛星數據
B+樹的衛星數據
2、B樹查找性能是不穩定的(如果要查找的數據分別在根節點和葉子節點,他們的性能就會不同)。但B+樹的每一次都是穩定的,為啥呢,看下面的范圍查詢。
范圍查詢:查找到范圍的下限(3)
B樹的范圍查詢:
自頂向下,查找到范圍的下限(3),最多6條:
中序遍歷到元素6:
中序遍歷到元素8:
中序遍歷到元素9:
中序遍歷到元素11,遍歷結束:
B+樹的范圍查詢:
自頂向下,查找到范圍的下限(3),最多6條:
通過鏈表指針,遍歷到元素6, 8:
通過鏈表指針,遍歷到元素9, 11,遍歷結束:
從上面的流程比較,可以得出以下B+樹的優勢:
1.單一節點存儲更多的元素,使得查詢的IO次數更少。
2.所有查詢都要查找到葉子節點,查詢性能穩定。
3.所有葉子節點形成有序鏈表,便於范圍查詢。
面試題
問題1:MySQL中存儲索引用到的數據結構是B+樹,B+樹的查詢時間跟樹的高度有關,是log(n),如果用hash存儲,那么查詢時間是O(1)。既然hash比B+樹更快,為什么mysql用B+樹來存儲索引呢?
答:一、從內存角度上說,數據庫中的索引一般時在磁盤上,數據量大的情況可能無法一次性裝入內存,B+樹的設計可以允許數據分批加載。
二、從業務場景上說,如果只選擇一個數據那確實是hash更快,但是數據庫中經常會選中多條這時候由於B+樹索引有序,並且又有鏈表相連,它的查詢效率比hash就快很多了。
問題2:為什么不用紅黑樹或者二叉排序樹?
答:樹的查詢時間跟樹的高度有關,B+樹是一棵多路搜索樹可以降低樹的高度,提高查找效率
問題3:既然增加樹的路數可以降低樹的高度,那么無限增加樹的路數是不是可以有最優的查找效率?
答:這樣會形成一個有序數組,文件系統和數據庫的索引都是存在硬盤上的,並且如果數據量大的話,不一定能一次性加載到內存中。有序數組沒法一次性加載進內存,這時候B+樹的多路存儲威力就出來了,可以每次加載B+樹的一個結點,然后一步步往下找,
問題4:在內存中,紅黑樹比B樹更優,但是涉及到磁盤操作B樹就更優了,那么你能講講B+樹嗎?
B+樹是在B樹的基礎上進行改造,它的數據都在葉子結點,同時葉子結點之間還加了指針形成鏈表。
下面是一個4路B+樹,它的數據都在葉子結點,並且有鏈表相連。
問題5:為什么B+樹要這樣設計?
答:這個跟它的使用場景有關,B+樹在數據庫的索引中用得比較多,數據庫中select數據,不一定只選一條,很多時候會選中多條,比如按照id進行排序后選100條。如果是多條的話,B+樹需要做局部的中序遍歷,可能要跨層訪問。而B+樹由於所有數據都在葉子結點不用跨層,同時由於有鏈表結構,只需要找到首尾,通過鏈表就能把所有數據取出來了。
比如選出7到19只需要在葉子結點中就能找到。
數據庫索引為什么要用 B+ 樹而不用紅黑樹呢?
AVL 樹和紅黑樹這些二叉樹結構的數據結構可以達到最高的查詢效率這是毋庸置疑的。
既然如此,那么數據庫索引為什么不用 AVL 樹或者紅黑樹呢?
這就牽扯到一個問題了,不考慮每種數據結構的前提條件而選擇數據結構都是在耍流氓。
AVL 數和紅黑樹基本都是存儲在內存中才會使用的數據結構,那磁盤中會有什么不同呢?
這就要牽扯到磁盤的存儲原理了
操作系統讀寫磁盤的基本單位是扇區,而文件系統的基本單位是簇(Cluster)。
也就是說,磁盤讀寫有一個最少內容的限制,即使我們只需要這個簇上的一個字節的內容,我們也要含着淚把一整個簇上的內容讀完。
那么,現在問題就來了
一個父節點只有 2 個子節點,並不能填滿一個簇上的所有內容啊?那多余的內容豈不是要浪費了?我們怎么才能把浪費的這部分內容利用起來呢?哈哈,答案就是 B+ 樹。
由於 B+ 樹分支比二叉樹更多,所以相同數量的內容,B+ 樹的深度更淺,深度代表什么?代表磁盤 io 次數啊!數據庫設計的時候 B+ 樹有多少個分支都是按照磁盤一個簇上最多能放多少節點設計的啊!
所以,涉及到磁盤上查詢的數據結構,一般都用 B+ 樹啦。