為什麼MySQL資料庫要用B+樹儲存索引?
要回答好這個問題,首先我們要弄懂什麼是索引?索引常見的資料結構有哪些?這些資料結構有何優缺點?只有弄懂這些,再去比較,才會知道為啥要用B+樹作為MySQL資料庫的儲存索引了。
一、索引是什麼?
MySQL官方對索引的定義為:索引(Index)是幫助MySQL高效獲取資料的資料結構。它的本質就是資料結構,單獨儲存在磁碟上,用它來提高資料查詢的效率。
二、索引常見資料結構
2.1 二叉查詢樹(Binary Search Tree)
採取二分查詢的思想,O(log N)的複雜度就可以完成對資料的查詢任務,查詢所需的最大次數等同於二叉查詢樹的高度。
它具有以下特性:
- 左子樹上所有結點的值均小於或等於它的根結點的值;
- 右子樹上所有結點的值均大於或等於它的根結點的值;
- 左、右子樹也分別為二叉排序樹。
如下圖所示:排序工具
對該二叉樹的節點進行查詢發現深度為1的節點的查詢次數為1,深度為2的查詢次數為2,深度為n的節點的查詢次數為n,因此其平均查詢次數為 (1+2+2+3+3+3) / 6 = 2.3次
二叉查詢樹可以任意地構造,這樣會出現一種極端情況,如果依次插入如下六個節點:7,6,5,4,那麼就會變成下圖所示:
這樣退化成線性表,導致樹高度過高,從而查詢效率就降低了。
那麼如何解決二叉查詢樹多次插入新節點而導致的不平衡?這裡就要引出新的定義——平衡二叉樹,或稱AVL樹。
樹的查詢效能取決於樹的高度,讓樹儘可能平衡,就是為了降低樹的高度。
2.2 平衡二叉查詢樹(AVL Tree)
平衡二叉查詢樹(AVL樹)在符合二叉查詢樹的條件下,還滿足任何節點的兩個子樹的高度最大差為1。如下圖所示,它的任何節點的兩個子樹的高度差<=1。
如果在AVL樹中進行插入或刪除節點,可能導致AVL樹失去平衡。
2.3 紅黑樹(Red Black Tree)
紅黑樹是一種自平衡的二叉查詢樹。除了符合二叉查詢樹的基本特性外,它還具備以下特性:
-
節點是紅色或者黑色;
- 根節點是黑色;
-
每個葉子的節點都是黑色的空節點(NULL);
-
每個紅色節點的兩個子節點都是黑色的;
-
從任意節點到其每個葉子的所有路徑都包含相同的黑色節點。
紅黑樹相比於BST和AVL樹有什麼優點?
紅黑樹是犧牲了嚴格的高度平衡的優越條件為代價,它只要求部分地達到平衡要求,降低了對旋轉的要求,從而提高了效能。
紅黑樹能夠以O(log2 n)的時間複雜度進行搜尋、插入、刪除操作。此外,由於它的設計,任何不平衡都會在三次旋轉之內解決。當然,還有一些更好的,但實現起來更復雜的資料結構能夠做到一步旋轉之內達到平衡,但紅黑樹能夠給我們一個比較“便宜”的解決方案。
相比於BST,因為紅黑樹可以能確保樹的最長路徑不大於兩倍的最短路徑的長度,所以可以看出它的查詢效果是有最低保證的。在最壞的情況下也可以保證O(logN)的,這是要好於二叉查詢樹的。因為二叉查詢樹最壞情況可以讓查詢達到O(N)。
紅黑樹的演算法時間複雜度和AVL相同,但統計效能比AVL樹更高,所以在插入和刪除中所做的後期維護操作肯定會比紅黑樹要耗時好多,但是他們的查詢效率都是O(logN),所以紅黑樹應用還是高於AVL樹的。實際上插入,AVL 樹和紅黑樹的速度取決於你所插入的資料.如果你的資料分佈較好,則比較宜於採用 AVL樹(例如隨機產生系列數),但是如果你想處理比較雜亂的情況,則紅黑樹是比較快的。
- AVL是嚴格平衡樹,因此在增加或者刪除節點的時候,根據不同情況,旋轉的次數比紅黑樹要多;
- 而紅黑是弱平衡的,用非嚴格的平衡來換取增刪節點時候旋轉次數的降低;
- 所以簡單說,查詢的次數遠遠大於插入和刪除,那麼選擇AVL樹;如果搜尋、插入刪除次數幾乎差不多,應該選擇RB樹。
紅黑樹的應用有很多,其中JDK的集合類TreeMap和TreeSet底層就是紅黑樹實現的。在JDK1.8中,連HashMap也用到了紅黑樹。
紅黑樹的各種操作的時間複雜度是O(lgn),邏輯上很近的節點(父子)物理上可能很遠,無法利用區域性性,IO次數多查詢慢,效率低。
2.4 平衡多路查詢樹(B-Tree)
紅黑樹的高度雖然有一定的控制,而資料庫當中一般要把索引樹的高度控制在3-5層,這點紅黑樹顯然無法做到。B-Tree是為磁碟等外儲存裝置設計的一種平衡查詢樹,是一種多路平衡搜尋樹,既然它是多路平衡的,那麼就不在像紅黑樹那樣只有2個子節點了,既然有多個子節點,樹的高度就可以控制了,同時它也跟紅黑樹一樣,資料是排序的,可以快速查詢。
B樹具有以下特點:
- 每個節點最多含有m個孩子;
- 根節點含有[2,m]個孩子;
- 非葉子節點含有[[m/2],m]個孩子節點(向上取整的意思);
- 一個節點如果含有K個關鍵字,那麼它就有k+1個孩子;
- 所有葉子節點都在同一層;
- 每個節點的K個關鍵數把節點拆成了K+1段
下面是一顆B樹:
B-Tree結構的資料可以讓系統高效的找到資料所在的磁碟塊。為了描述B-Tree,首先定義一條記錄為一個二元組[key, data] ,key為記錄的鍵值,對應表中的主鍵值,data為一行記錄中除主鍵外的資料。對於不同的記錄,key值互不相同。
B-Tree中的每個節點根據實際情況可以包含大量的關鍵字資訊和分支,如下圖所示為一個3階的B-Tree:
每個節點佔用一個盤塊的磁碟空間,一個節點上有兩個升序排序的關鍵字和三個指向子樹根節點的指標,指標儲存的是子節點所在磁碟塊的地址。兩個關鍵詞劃分成的三個範圍域對應三個指標指向的子樹的資料的範圍域。以根節點為例,關鍵字為17和35,P1指標指向的子樹的資料範圍為小於17,P2指標指向的子樹的資料範圍為17~35,P3指標指向的子樹的資料範圍為大於35。
模擬查詢關鍵字29的過程:
- 根據根節點找到磁碟塊1,讀入記憶體。【磁碟I/O操作第1次】
- 比較關鍵字29在區間(17,35),找到磁碟塊1的指標P2。
- 根據P2指標找到磁碟塊3,讀入記憶體。【磁碟I/O操作第2次】
- 比較關鍵字29在區間(26,30),找到磁碟塊3的指標P2。
- 根據P2指標找到磁碟塊8,讀入記憶體。【磁碟I/O操作第3次】
- 在磁碟塊8中的關鍵字列表中找到關鍵字29。
分析上面過程,發現需要3次磁碟I/O操作,和3次記憶體查詢操作。由於記憶體中的關鍵字是一個有序表結構,可以利用二分法查詢提高效率。而3次磁碟I/O操作是影響整個B-Tree查詢效率的決定因素。
2.5 B+Tree
B+Tree是在B-Tree(不要讀成B減樹,而是B樹)基礎上的一種優化,使其更適合實現外儲存索引結構,InnoDB儲存引擎就是用B+Tree實現其索引結構。
從上一節中的B-Tree結構圖中可以看到每個節點中不僅包含資料的key值,還有data值。而每一個頁的儲存空間是有限的,如果data資料較大時將會導致每個節點(即一個頁)能儲存的key的數量很小,當儲存的資料量很大時同樣會導致B-Tree的深度較大,增大查詢時的磁碟I/O次數,進而影響查詢效率。在B+Tree中,所有資料記錄節點都是按照鍵值大小順序存放在同一層的葉子節點上,而非葉子節點上只儲存key值資訊,這樣可以大大加大每個節點儲存的key值數量,降低B+Tree的高度。
B+Tree相對於B-Tree有幾點不同:
- 非葉子節點只儲存鍵值資訊。
- 所有葉子節點之間都有一個鏈指標。
- 資料記錄都存放在葉子節點中。
將上一節中的B-Tree優化,由於B+Tree的非葉子節點只儲存鍵值資訊,假設每個磁碟塊能儲存4個鍵值及指標資訊,則變成B+Tree後其結構如下圖所示:
資料都在葉子節點上,並且增加了順序訪問指標,每個葉子節點都指向相鄰的葉子節點的地址。相比B-Tree來說,進行範圍查詢時只需要查詢兩個節點,進行遍歷即可,提高了區間訪問效能(無需返回上層父節點重複遍歷查詢減少IO操作)。而B-Tree需要獲取所有節點,相比之下B+Tree效率更高。
三、為什麼使用B+Tree
紅黑樹等資料結構也可以用來實現索引,但是檔案系統及資料庫系統普遍採用B-/+Tree作為索引結構。
一般來說,索引本身也很大,不可能全部儲存在記憶體中,因此索引往往以索引檔案的形式儲存的磁碟上。這樣的話,索引查詢過程中就要產生磁碟I/O消耗,相對於記憶體存取,I/O存取的消耗要高几個數量級,所以評價一個數據結構作為索引的優劣最重要的指標就是在查詢過程中磁碟I/O操作次數的漸進複雜度。換句話說,索引的結構組織要儘量減少查詢過程中磁碟I/O的存取次數。
這樣我們對比上面的B+樹和紅黑樹,比如查詢節點21,紅黑樹要磁碟IO5次,而B+樹只要2次,也就是說磁碟IO次數大致為樹的高度,這樣B+樹就脫穎而出了,成為實現索引的不二選擇。
實際情況中每個節點可能不能填充滿,因此在資料庫中,B+Tree的高度一般都在2~4層。MySQL的InnoDB儲存引擎在設計時是將根節點常駐記憶體的,也就是說查詢某一鍵值的行記錄時最多隻需要1~3次磁碟I/O操作。
資料庫中的B+Tree索引可以分為聚集索引(clustered index)和輔助索引(secondary index)。上面的B+Tree示例圖在資料庫中的實現即為聚集索引,聚集索引的B+Tree中的葉子節點存放的是整張表的行記錄資料。輔助索引與聚集索引的區別在於輔助索引的葉子節點並不包含行記錄的全部資料,而是儲存相應行資料的聚集索引鍵,即主鍵。當通過輔助索引來查詢資料時,InnoDB儲存引擎會遍歷輔助索引找到主鍵,然後再通過主鍵在聚集索引中找到完整的行記錄資料。
聚簇索引(聚集索引):並不是一種單獨的索引型別,而是一種資料儲存方式。具體細節取決於不同的實現,InnoDB的聚簇索引其實就是在同一個結構中儲存了B-Tree索引(技術上來說是B+Tree)和資料行。
資料庫索引採用B+樹而不是B樹的主要原因:B+樹只要遍歷葉子節點就可以實現整棵樹的遍歷,而且在資料庫中基於範圍的查詢是非常頻繁的,而B樹只能中序遍歷所有節點,效率太低。
檔案與資料庫都是需要較大的儲存,也就是說,它們都不可能全部儲存在記憶體中,故需要儲存到磁碟上。而所謂索引,則為了資料的快速定位與查詢,那麼索引的結構組織要儘量減少查詢過程中磁碟I/O的存取次數,因此B+樹相比B樹更為合適。資料庫系統巧妙利用了局部性原理與磁碟預讀原理,將一個節點的大小設為等於一個頁,這樣每個節點只需要一次I/O就可以完全載入,而紅黑樹這種結構,高度明顯要深的多,並且由於邏輯上很近的節點(父子)物理上可能很遠,無法利用區域性性。最重要的是,B+樹還有一個最大的好處:方便掃庫。B樹必須用中序遍歷的方法按序掃庫,而B+樹直接從葉子結點挨個掃一遍就完了,B+樹支援range-query非常方便,而B樹不支援,這是資料庫選用B+樹的最主要原因。
四、問題
問:為什麼索引結構預設使用B-Tree,而不是hash,二叉樹,紅黑樹?
hash:雖然可以快速定位,但是沒有順序,IO複雜度高。
二叉樹:樹的高度不均勻,不能自平衡,查詢效率跟資料有關(樹的高度),並且IO代價高。
紅黑樹:樹的高度隨著資料量增加而增加,IO代價高。
問:為什麼官方建議使用自增長主鍵作為索引。
結合B+Tree的特點,自增主鍵是連續的,在插入過程中儘量減少頁分裂,即使要進行頁分裂,也只會分裂很少一部分。並且能減少資料的移動,每次插入都是插入到最後。總之就是減少分裂和移動的頻率。
參考:
https://blog.csdn.net/majiawenzzz/article/details/81098870
https://blog.csdn.net/UFO___/article/details/80522453
https://www.cnblogs.com/liqiangchn/p/9060521.html
https://www.jianshu.com/p/486a514b0ded
https://blog.csdn.net/ifollowrivers/article/details/73614549