理解MySQL索引的底層實現原理


理解索引的特性

  • 索引是幫助MySQL高效獲取數據的排好序的數據結構
  • 索引存儲在文件裏

MySQL支持多種索引類型,如BTree索引哈希索引全文索引等等。

本文主要討論BTree索引,這也是我們平時用得最多的索引。


索引的本質

MySQL官方對於索引的定義爲:索引是幫助MySQL高效獲取數據的數據結構。 即可以理解爲:索引是數據結構

我們知道,數據庫查詢是數據庫最主要的功能之一,我們都希望查詢數據的速度儘可能的快,因此數據庫系統的設計者會從查詢算法的角度進行優化。最基本的查詢算法當然是順序查找,當然這種時間複雜度爲O(n)的算法在數據量很大時顯然是糟糕的,於是有了二分查找、二叉樹查找等。

但是二分查找要求被檢索數據有序,而二叉樹查找只能應用於二叉查找樹,但是數據本身的組織結構不可能完全滿足各種數據結構。所以,在數據之外,數據庫系統還維護着滿足特定查找算法的數據結構,這些數據結構以某種方式引用數據,這樣就可以在這些數據結構上實現高級查找算法。這種數據結構,就是索引

接下來看一個示例:

上圖展示了一種可能的索引方式。左邊是數據表,一共有兩列七條記錄,最左邊的是數據記錄的物理地址(注意邏輯上相鄰的記錄在磁盤上也並不是一定物理相鄰的)。爲了加快Col2的查找,可以維護一個右邊所示的二叉查找樹,每個節點分別包含索引鍵值和一個指向對應數據記錄物理地址的指針,這樣就可以運用二叉查找在O(logn2)O(log2n)的複雜度內獲取到相應數據。

雖然這是一個貨真價實的索引,但是實際的數據庫系統幾乎沒有使用二叉查找樹或其進化品種紅黑樹(red-black tree)實現的,原因會在下文介紹。

本文將只關注於BTree索引,因爲這是平常使用MySQL時主要使用到的索引。


其他結構的問題

由於索引無法裝入內存,則必然依賴磁盤(或SSD)存儲。而內存的讀寫速度是磁盤的成千上萬倍(與具體實現有關),因此,核心問題是“如何減少磁盤讀寫次數”。
首先不考慮頁表機制,假設每次讀、寫都直接穿透到磁盤,那麼:

  • 線性結構:讀/寫平均O(n)次
  • 二叉搜索樹(BST):讀/寫平均O(log2(n))次;如果樹不平衡,則最差讀/寫O(n)次
  • 自平衡二叉搜索樹(AVL):在BST的基礎上加入了自平衡算法,讀/寫最大O(log2(n))次
  • 紅黑樹(RBT):另一種自平衡的查找樹,讀/寫最大O(log2(n))次

BST、AVL、RBT很好的將讀寫次數從O(n)優化到O(log2(n));其中,AVL和RBT都比BST多了自平衡的功能,將讀寫次數降到最大O(log2(n))。

假設使用自增主鍵,則主鍵本身是有序的,樹結構的讀寫次數能夠優化到樹高樹高越低讀寫次數越少;自平衡保證了樹結構的穩定。如果想進一步優化,可以引入B樹和B+樹。

在介紹B樹之前,先來看另一棵神奇的樹——二叉排序樹(Binary Sort Tree),首先它是一棵樹,“二叉”這個描述已經很明顯了,就是樹上的一根樹枝開兩個叉,於是遞歸下來就是二叉樹了(下圖所示),而這棵樹上的節點是已經排好序的,具體的排序規則如下:

  • 若左子樹不空,則左子樹上所有節點的值均小於它的根節點的值
  • 若右子樹不空,則右字數上所有節點的值均大於它的根節點的值
  • 它的左、右子樹也分別爲二叉排序樹(遞歸定義)

從圖中可以看出,二叉排序樹組織數據時,用於查找是比較方便的,因爲每次經過一次節點時,最多可以減少一半的可能,不過極端情況會出現所有節點都位於同一側,直觀上看就是一條直線,那麼這種查詢的效率就比較低了,因此需要對二叉樹左右子樹的高度進行平衡化處理,於是就有了平衡二叉樹(Balenced Binary Tree)

所謂“平衡”,說的是這棵樹的各個分支的高度是均勻的,它的左子樹和右子樹的高度之差絕對值小於1,這樣就不會出現一條支路特別長的情況。於是,在這樣的平衡樹中進行查找時,總共比較節點的次數不超過樹的高度,這就確保了查詢的效率(時間複雜度爲O(logn))。

參考:https://blog.csdn.net/chuixue24/article/details/86594069


B-Tree 和 B+Tree

目前大部分數據庫系統及文件系統都採用 B-Tree 和 B+Tree 作爲索引結構。

MySQL InnoDB存儲引擎是基於B-樹(但實際上MySQL採用的是B+樹結構)的索引結構。

注意B-Tree 中文稱爲 B樹沒有所謂的B減樹,特別是與B+樹一起講的時候。想當然的認爲有B+(加)樹就有B-(減)樹,實際上B+樹的英文名是“B+ -Tree”。

B-樹是一種m階平衡樹,葉子節點都在同一層,由於每一個節點存儲的數據量比較大,索引整個B-樹的層數是非常低的,基本上不超過三層。

由於磁盤的讀取也是按block塊操作的(內存是按page頁面操作的),因此B-樹的節點大小一般設置爲和磁盤塊大小一致,這樣一個B-樹節點,就可以通過一次磁盤I/O把一個磁盤塊的數據全部存儲下來,所以當使用B-樹存儲索引的時候,磁盤I/O的操作次數是最少的(MySQL的讀寫效率,主要集中在磁盤I/O上)。

那麼MySQL最終爲什麼要採用B+樹存儲索引結構呢,那麼看看B-樹和B+樹在存儲結構上有什麼不同?

  • B-樹的每一個節點,存了關鍵字和對應的數據地址,而B+樹的非葉子節點只存關鍵字,不存數據地址。因此B+樹的每一個非葉子節點存儲的關鍵字是遠遠多於B-樹的,B+樹的葉子節點存放關鍵字和數據,因此,從樹的高度上來說,B+樹的高度要小於B-樹,使用的磁盤I/O次數少,因此查詢會更快一些
  • B-樹由於每個節點都存儲關鍵字和數據,因此離根節點進的數據,查詢的就快,離根節點遠的數據,查詢的就慢;B+樹所有的數據都存在葉子節點上,因此在B+樹上搜索關鍵字,找到對應數據的時間是比較平均的,沒有快慢之分
  • 在B-樹上如果做區間查找,遍歷的節點是非常多的;B+樹所有葉子節點被連接成了有序鏈表結構,因此做整表遍歷和區間查找是非常容易的

B-樹的結構圖如下:

B+樹的結構圖如下:


MySQL索引實現

在MySQL中,索引屬於存儲引擎級別的概念,不同存儲引擎對索引的實現方式是不同的,接下來我們主要討論MyISAM和InnoDB兩個存儲引擎的索引實現方式。


MyISAM索引實現

MyISAM引擎使用B+Tree作爲索引結構,葉節點的data域存放的是數據記錄的地址。下圖是MyISAM索引的原理圖:

這裏設表一共有三列,假設我們以Col1爲主鍵,則圖8是一個MyISAM表的主索引(Primary key)示意。可以看出MyISAM的索引文件僅僅保存數據記錄的地址。在MyISAM中,主索引和輔助索引(Secondary key)在結構上沒有任何區別,只是主索引要求key是唯一的,而輔助索引的key可以重複。如果我們在Col2上建立一個輔助索引,則此索引的結構如下圖所示:

同樣也是一顆B+Tree,data域保存數據記錄的地址。因此,MyISAM中索引檢索的算法爲首先按照B+Tree搜索算法搜索索引,如果指定的Key存在,則取出其data域的值,然後以data域的值爲地址,讀取相應數據記錄

MyISAM的索引方式也叫做“非聚集”的,之所以這麼稱呼是爲了與InnoDB的聚集索引區分。


InnoDB索引實現

雖然InnoDB也使用B+Tree作爲索引結構,但具體實現方式卻與MyISAM截然不同。

第一個重大區別是InnoDB的數據文件本身就是索引文件。從上文知道,MyISAM索引文件和數據文件是分離的,索引文件僅保存數據記錄的地址。在InnoDB中,表數據文件本身就是按B+Tree組織的一個索引結構,這棵樹的葉節點data域保存了完整的數據記錄。這個索引的key是數據表的主鍵,因此InnoDB表數據文件本身就是主索引。

上圖是InnoDB主索引(同時也是數據文件)的示意圖,可以看到葉節點包含了完整的數據記錄。這種索引叫做聚集索引。因爲InnoDB的數據文件本身要按主鍵聚集,所以InnoDB要求表必須有主鍵(MyISAM可以沒有),如果沒有顯式指定,則MySQL系統會自動選擇一個可以唯一標識數據記錄的列作爲主鍵,如果不存在這種列,則MySQL自動爲InnoDB表生成一個隱含字段作爲主鍵,這個字段長度爲6個字節,類型爲長整形。

第二個與MyISAM索引的不同是InnoDB的輔助索引data域存儲相應記錄主鍵的值而不是地址。換句話說,InnoDB的所有輔助索引都引用主鍵作爲data域。例如,下圖爲定義在Col3上的一個輔助索引:

這裏以英文字符的ASCII碼作爲比較準則。聚集索引這種實現方式使得按主鍵的搜索十分高效,但是輔助索引搜索需要檢索兩遍索引:首先檢索輔助索引獲得主鍵,然後用主鍵到主索引中檢索獲得記錄

瞭解不同存儲引擎的索引實現方式對於正確使用和優化索引都非常有幫助,例如知道了InnoDB的索引實現後,就很容易明白爲什麼不建議使用過長的字段作爲主鍵,因爲所有輔助索引都引用主索引,過長的主索引會令輔助索引變得過大。再例如,用非單調的字段作爲主鍵在InnoDB中不是個好主意,因爲InnoDB數據文件本身是一顆B+Tree,非單調的主鍵會造成在插入新記錄時數據文件爲了維持B+Tree的特性而頻繁的分裂調整,十分低效,而使用自增字段作爲主鍵則是一個很好的選擇

下篇博文我們將具體討論這些與索引有關的使用和優化策略。

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