MySQL用B+樹作為索引結構有什麼好處
前言
在MySQL中,無論是Innodb還是MyIsam,都使用了B+樹作索引結構(這裡不考慮hash等其他索引)。本文將從最普通的二叉查詢樹開始,逐步說明各種樹解決的問題以及面臨的新問題,從而說明MySQL為什麼選擇B+樹作為索引結構。
一、二叉查詢樹(BST):不平衡
二叉查詢樹(BST,Binary Search Tree),也叫二叉排序樹,在二叉樹的基礎上需要滿足:任意節點的左子樹上所有節點值不大於根節點的值,任意節點的右子樹上所有節點值不小於根節點的值。如下是一顆BST:
當需要快速查詢時,將資料儲存在BST是一種常見的選擇,因為此時查詢時間取決於樹高,平均時間複雜度是O(lgn)。然而,BST可能長歪而變得不平衡,如下圖所示,此時BST退化為連結串列,時間複雜度退化為O(n)。
為了解決這個問題,引入了平衡二叉樹。
二、平衡二叉樹(AVL):旋轉耗時
AVL樹是嚴格的平衡二叉樹,所有節點的左右子樹高度差不能超過1;AVL樹查詢、插入和刪除在平均和最壞情況下都是O(lgn)。
AVL實現平衡的關鍵在於旋轉操作:插入和刪除可能破壞二叉樹的平衡,此時需要通過一次或多次樹旋轉來重新平衡這個樹。當插入資料時,最多隻需要1次旋轉(單旋轉或雙旋轉);但是當刪除資料時,會導致樹失衡,AVL需要維護從被刪除節點到根節點這條路徑上所有節點的平衡,旋轉的量級為O(lgn)。
由於旋轉的耗時,AVL樹在刪除資料時效率很低;在刪除操作較多時,維護平衡所需的代價可能高於其帶來的好處,因此AVL實際使用並不廣泛。
三、紅黑樹:樹太高
與AVL樹相比,紅黑樹並不追求嚴格的平衡,而是大致的平衡:只是確保從根到葉子的最長的可能路徑不多於最短的可能路徑的兩倍長。從實現來看,紅黑樹最大的特點是每個節點都屬於兩種顏色(紅色或黑色)之一,且節點顏色的劃分需要滿足特定的規則(具體規則略)。紅黑樹示例如下:
與AVL樹相比,紅黑樹的查詢效率會有所下降,這是因為樹的平衡性變差,高度更高。但紅黑樹的刪除效率大大提高了,因為紅黑樹同時引入了顏色,當插入或刪除資料時,只需要進行O(1)次數的旋轉以及變色就能保證基本的平衡,不需要像AVL樹進行O(lgn)次數的旋轉。總的來說,紅黑樹的統計效能高於AVL。
因此,在實際應用中,AVL樹的使用相對較少,而紅黑樹的使用非常廣泛。例如,Java中的TreeMap使用紅黑樹儲存排序鍵值對;Java8中的HashMap使用連結串列+紅黑樹解決雜湊衝突問題(當衝突節點較少時,使用連結串列,當衝突節點較多時,使用紅黑樹)。
對於資料在記憶體中的情況(如上述的TreeMap和HashMap),紅黑樹的表現是非常優異的。但是對於資料在磁碟等輔助儲存裝置中的情況(如MySQL等資料庫),紅黑樹並不擅長,因為紅黑樹長得還是太高了。當資料在磁碟中時,磁碟IO會成為最大的效能瓶頸,設計的目標應該是儘量減少IO次數;而樹的高度越高,增刪改查所需要的IO次數也越多,會嚴重影響效能。
四、B樹:為磁碟而生
B樹也稱B-樹(其中不是減號),是為磁碟等輔存裝置設計的多路平衡查詢樹,與二叉樹相比,樹的每個非葉節點可以有多個子樹。因此,當總節點數量相同時,B樹的高度遠遠小於AVL樹和紅黑樹(B樹是一顆“矮胖子”),磁碟IO次數大大減少。
定義B樹最重要的概念是階數(Order),對於一顆m階B樹,需要滿足以下條件:
- 每個節點最多包含 m 個子節點。
- 如果根節點包含子節點,則至少包含 2 個子節點;除根節點外,每個非葉節點至少包含 m/2 個子節點。
- 擁有 k 個子節點的非葉節點將包含 k - 1 條記錄。
- 所有葉節點都在同一層中。
可以看出,B樹的定義,主要是對非葉結點的子節點數量和記錄數量的限制。
下圖是一個3階B樹的例子:
B樹的優勢除了樹高小,還有對訪問區域性性原理的利用。所謂區域性性原理,是指當一個數據被使用時,其附近的資料有較大概率在短時間內被使用。B樹將鍵相近的資料儲存在同一個節點,當訪問其中某個資料時,資料庫會將該整個節點讀到快取中;當它臨近的資料緊接著被訪問時,可以直接在快取中讀取,無需進行磁碟IO;換句話說,B樹的快取命中率更高。
B樹在資料庫中有一些應用,如mongodb的索引使用了B樹結構。但是在很多資料庫應用中,使用了是B樹的變種B+樹。
五、B+樹
B+樹也是多路平衡查詢樹,其與B樹的區別主要在於:
- B樹中每個節點(包括葉節點和非葉節點)都儲存真實的資料,B+樹中只有葉子節點儲存真實的資料,非葉節點只儲存鍵。在MySQL中,這裡所說的真實資料,可能是行的全部資料(如Innodb的聚簇索引),也可能只是行的主鍵(如Innodb的輔助索引),或者是行所在的地址(如MyIsam的非聚簇索引)。
- B樹中一條記錄只會出現一次,不會重複出現,而B+樹的鍵則可能重複重現——一定會在葉節點出現,也可能在非葉節點重複出現。
- B+樹的葉節點之間通過雙向連結串列連結。
- B樹中的非葉節點,記錄數比子節點個數少1;而B+樹中記錄數與子節點個數相同。
由此,B+樹與B樹相比,有以下優勢:
- **更少的IO次數:**B+樹的非葉節點只包含鍵,而不包含真實資料,因此每個節點儲存的記錄個數比B數多很多(即階m更大),因此B+樹的高度更低,訪問時所需要的IO次數更少。此外,由於每個節點儲存的記錄數更多,所以對訪問區域性性原理的利用更好,快取命中率更高。
- **更適於範圍查詢:**在B樹中進行範圍查詢時,首先找到要查詢的下限,然後對B樹進行中序遍歷,直到找到查詢的上限;而B+樹的範圍查詢,只需要對連結串列進行遍歷即可。
- **更穩定的查詢效率:**B樹的查詢時間複雜度在1到樹高之間(分別對應記錄在根節點和葉節點),而B+樹的查詢複雜度則穩定為樹高,因為所有資料都在葉節點。
B+樹也存在劣勢:由於鍵會重複出現,因此會佔用更多的空間。但是與帶來的效能優勢相比,空間劣勢往往可以接受,因此B+樹的在資料庫中的使用比B樹更加廣泛。
六、感受B+樹的威力
前面說到,B樹/B+樹與紅黑樹等二叉樹相比,最大的優勢在於樹高更小。實際上,對於Innodb的B+索引來說,樹的高度一般在2-4層。下面來進行一些具體的估算。
樹的高度是由階數決定的,階數越大樹越矮;而階數的大小又取決於每個節點可以儲存多少條記錄。Innodb中每個節點使用一個頁(page),頁的大小為16KB,其中元資料只佔大約128位元組左右(包括檔案管理頭資訊、頁面頭資訊等等),大多數空間都用來儲存資料。
- 對於非葉節點,記錄只包含索引的鍵和指向下一層節點的指標。假設每個非葉節點頁面儲存1000條記錄,則每條記錄大約佔用16位元組;當索引是整型或較短的字串時,這個假設是合理的。延伸一下,我們經常聽到建議說索引列長度不應過大,原因就在這裡:索引列太長,每個節點包含的記錄數太少,會導致樹太高,索引的效果會大打折扣,而且索引還會浪費更多的空間。
- 對於葉節點,記錄包含了索引的鍵和值(值可能是行的主鍵、一行完整資料等,具體見前文),資料量更大。這裡假設每個葉節點頁面儲存100條記錄(實際上,當索引為聚簇索引時,這個數字可能不足100;當索引為輔助索引時,這個數字可能遠大於100;可以根據實際情況進行估算)。
對於一顆3層B+樹,第一層(根節點)有1個頁面,可以儲存1000條記錄;第二層有1000個頁面,可以儲存1000 * 1000條記錄;第三層(葉節點)有1000 * 1000個頁面,每個頁面可以儲存100條記錄,因此可以儲存1000 * 1000 * 100條記錄,即1億條。而對於二叉樹,儲存1億條記錄則需要26層左右。
七、總結
最後,總結一下各種樹解決的問題以及面臨的新問題:
- 二叉查詢樹(BST):解決了排序的基本問題,但是由於無法保證平衡,可能退化為連結串列;
- 平衡二叉樹(AVL):通過旋轉解決了平衡的問題,但是旋轉操作效率太低;
- 紅黑樹:通過捨棄嚴格的平衡和引入紅黑節點,解決了AVL旋轉效率過低的問題,但是在磁碟等場景下,樹仍然太高,IO次數太多
- B樹:通過將二叉樹改為多路平衡查詢樹,解決了樹過高的問題;
- B+樹:在B樹的基礎上,將非葉節點改造為不儲存資料的純索引節點,進一步降低了樹的高度;此外將葉節點使用指標連線成連結串列,範圍查詢更加高效。
以上就是MySQL用B+樹作為索引結構有什麼好處的詳細內容,更多關於MySQL B+樹索引結構的資料請關注我們其它相關文章!