MySQL使用B+樹存儲索引

一、索引是什麼?

MySQL官方對索引的定義爲:索引(Index)是幫助MySQL高效獲取數據的數據結構。它的本質就是數據結構,單獨存儲在磁盤上,用它來提高數據查詢的效率。

適合作爲索引的結構應該是儘可能少的執行磁盤IO操作,因爲執行磁盤IO操作非常的耗時。

二、索引常見數據結構

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)

紅黑樹是一種自平衡的二叉查找樹。除了符合二叉查找樹的基本特性外,它還具備以下特性:

  1. 節點是紅色或者黑色;

  2. 根節點是黑色;
  3. 每個葉子的節點都是黑色的空節點(NULL);

  4. 每個紅色節點的兩個子節點都是黑色的;

  5. 從任意節點到其每個葉子的所有路徑都包含相同的黑色節點。

五分鐘搞懂什麼是紅黑樹(全程圖解)

紅黑樹相比於BST和AVL樹有什麼優點?

紅黑樹是犧牲了嚴格的高度平衡的優越條件爲代價,它只要求部分地達到平衡要求,降低了對旋轉的要求,從而提高了性能。

紅黑樹能夠以O(log2 n)的時間複雜度進行搜索、插入、刪除操作。此外,由於它的設計,任何不平衡都會在三次旋轉之內解決。當然,還有一些更好的,但實現起來更復雜的數據結構能夠做到一步旋轉之內達到平衡,但紅黑樹能夠給我們一個比較“便宜”的解決方案。

相比於BST,因爲紅黑樹可以能確保樹的最長路徑不大於兩倍的最短路徑的長度,所以可以看出它的查找效果是有最低保證的。在最壞的情況下也可以保證O(logN)的,這是要好於二叉查找樹的。因爲二叉查找樹最壞情況可以讓查找達到O(N)。

紅黑樹的算法時間複雜度和AVL相同,但統計性能比AVL樹更高,所以在插入和刪除中所做的後期維護操作肯定會比紅黑樹要耗時好多,但是他們的查找效率都是O(logN),所以紅黑樹應用還是高於AVL樹的。實際上插入,AVL 樹和紅黑樹的速度取決於你所插入的數據.如果你的數據分佈較好,則比較宜於採用 AVL樹(例如隨機產生系列數),但是如果你想處理比較雜亂的情況,則紅黑樹是比較快的。

  • AVL是嚴格平衡樹,因此在增加或者刪除節點的時候,根據不同情況,旋轉的次數比紅黑樹要多;
  • 而紅黑是弱平衡的,用非嚴格的平衡來換取增刪節點時候旋轉次數的降低;
  • 所以簡單說,查找的次數遠遠大於插入和刪除,那麼選擇AVL樹;如果搜索、插入刪除次數幾乎差不多,應該選擇RB樹。 

紅黑樹的應用

  1. 在Java中, TreeMap和TreeSet,Java 8中HashMap中TreeNode節點都採用了紅黑樹實現。 
  2. C++中,STL的map和set也應用了紅黑樹; 
  3. Linux進程調度Completely Fair Scheduler; 
  4. 用紅黑樹管理進程控制塊epoll在內核中的實現,用紅黑樹管理事件塊; 
  5. Nginx中,用紅黑樹管理timer等;

紅黑樹的各種操作的時間複雜度是O(lgn),邏輯上很近的節點(父子)物理上可能很遠,無法利用局部性,IO次數多查找慢,效率低。

2.4 平衡多路查找樹(B-Tree)

紅黑樹的高度雖然有一定的控制,而數據庫當中一般要把索引樹的高度控制在3-5層,這點紅黑樹顯然無法做到。B-Tree是爲磁盤等外存儲設備設計的一種平衡查找樹,是一種多路平衡搜索樹,既然它是多路平衡的,那麼就不在像紅黑樹那樣只有2個子節點了,既然有多個子節點,樹的高度就可以控制了,同時它也跟紅黑樹一樣,數據是排序的,可以快速查找。

B樹具有以下特點:

  1. 每個節點最多含有m個孩子;
  2. 根節點含有[2,m]個孩子;
  3. 非葉子節點含有[[m/2],m]個孩子節點(向上取整的意思);
  4. 一個節點如果含有K個關鍵字,那麼它就有k+1個孩子;
  5. 所有葉子節點都在同一層;
  6. 每個節點的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. 根據根節點找到磁盤塊1,讀入內存。【磁盤I/O操作第1次】
  2. 比較關鍵字29在區間(17,35),找到磁盤塊1的指針P2。
  3. 根據P2指針找到磁盤塊3,讀入內存。【磁盤I/O操作第2次】
  4. 比較關鍵字29在區間(26,30),找到磁盤塊3的指針P2。
  5. 根據P2指針找到磁盤塊8,讀入內存。【磁盤I/O操作第3次】
  6. 在磁盤塊8中的關鍵字列表中找到關鍵字29。

分析上面過程,發現需要3次磁盤I/O操作,和3次內存查找操作。由於內存中的關鍵字是一個有序表結構,可以利用二分法查找提高效率。而3次磁盤I/O操作是影響整個B-Tree查找效率的決定因素

MySQL中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有幾點不同:

  1. 非葉子節點只存儲鍵值信息。
  2. 所有葉子節點之間都有一個鏈指針。
  3. 數據記錄都存放在葉子節點中。

將上一節中的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代價高。

如果只選一個數據,那確實是hash更快。但是數據庫中經常會選擇多條,這時候由於B+樹索引有序,並且又有鏈表相連,它的查詢效率比hash就快很多了。

而且數據庫中的索引一般是在磁盤上,數據量大的情況可能無法一次裝入內存,B+樹的設計可以允許數據分批加載,同時樹的高度較低,提高查找效率。

問:爲什麼官方建議使用自增長主鍵作爲索引。

結合B+Tree的特點,自增主鍵是連續的,在插入過程中儘量減少頁分裂,即使要進行頁分裂,也只會分裂很少一部分。並且能減少數據的移動,每次插入都是插入到最後。總之就是減少分裂和移動的頻率。

UUID

好處就是本地生成,不要基於數據庫來了;不好之處就是,UUID 太長了、佔用空間大,作爲主鍵性能太差了;更重要的是,UUID 不具有有序性,會導致 B+ 樹索引在寫的時候有過多的隨機寫操作(連續的 ID 可以產生部分順序寫),還有,由於在寫的時候不能產生有順序的 append 操作,而需要進行 insert 操作,將會讀取整個 B+ 樹節點到內存,在插入這條記錄後會將整個節點寫回磁盤,這種操作在記錄佔用空間比較大的情況下,性能下降明顯。

 

源:https://my.oschina.net/lienson/blog/2987474

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