1. 程式人生 > >深入理解MySQL索引底層資料結構與演算法

深入理解MySQL索引底層資料結構與演算法

一 理解索引的特性

  • 索引是幫助MySQL高效獲取資料的排好序資料結構
  • 索引儲存在檔案裡

二 索引的各種儲存結構及其優缺點

在開始講這一小節之前,我們先來看一下在資料庫沒有加索引的情況下,SQL中的where字句是如何查詢目標記錄的。

我們先看下左邊表格第二列Col2列的資料時如何查詢的,如果我怕希望查詢where Col2 = 22的記錄,我們在沒加索引的情況下是按順序從第一條記錄查詢,由此可知需要查詢5次才能找到;

如果對Col2欄位加上索引後,我們假設使用最簡單的二叉樹作為索引儲存方式,再次查詢where Col2 = 22的記錄這次只需要查詢2次就能找到目標記錄,效率提高十分明顯。

(一) 二叉樹

1. 優點:

二叉樹是一種比順序結構更加高效地查詢目標元素的結構,它可以從第一個父節點開始跟目標元素值比較,如果相等則返回當前節點,如果目標元素值小於當前節點,則移動到左側子節點進行比較,大於的情況則移動到右側子節點進行比較,反覆進行操作最終移動到目標元素節點位置。

2. 缺點:

在大部分情況下,我們設計索引時都會在表中提供一個自增整形欄位作為建立索引的列,在這種場景下使用二叉樹的結構會導致我們的索引總是新增到右側,在查詢記錄時跟沒加索引的情況是一樣的,如下圖所示:

(二) 紅黑樹

1. 優點:

紅黑樹也叫平衡二叉樹,它不僅繼承了二叉樹的有點,而且解決了上面二叉樹遇到的自增整形索引的問題,從下面的動態圖中可以看出紅黑樹會走動對結構進行調整,始終保證左子節點 < 父節點 < 右子節點

的規則。

2. 缺點:

在資料量大的時候,深度也很大。從圖中可以看出每個父節點只能存在兩個子節點,如果我們有很多資料,那麼樹的深度依然會很大,可能就會超過十幾二十層以上,對我們的磁碟定址不利,依然會花費很多時間查詢。

(三) Hash

1. 優點:

對資料進行Hash(雜湊)運算,主流的Hash演算法有MD5、SHA256等等,然後將雜湊結果作為檔案指標可以從索引檔案中獲得資料的檔案指標,再到資料檔案中獲取到資料,按照這樣的設計,我們在查詢where Col2 = 22的記錄時只需要對22做雜湊運算得到該索引所對應那行資料的檔案指標,從而在MySQL的資料檔案中定位到目標記錄,查詢效率非常高。

2. 缺點:

無法解決範圍查詢(Range)的場景,比如 select count(id) from sus_user where id >10;因此Hash這種索引結構只能針對欄位名=目標值的場景使用。

不適合模糊查詢(like)的場景。

(四) B-Tree

既然紅黑樹存在缺點,那麼我們可以在紅黑樹的基礎上構思一種新的儲存結構。解決的思路也很簡單,既然覺得樹的深度太長,就只需要適當地增加每個樹節點能儲存的資料個數即可,但是資料個數也必須要設定一個合理的閾值,不然一個節點資料個數過多會產生多餘的消耗。

按照這樣的思路,我們先來了解下關於B-Tree的一些知識點:

  • 度(Degree)-節點的資料儲存個數,每個樹節點中資料個數大於 15/16*Degree(未驗證) 時會自動分裂,調整結構
  • 葉節點具有相同的深度,左子樹跟右子樹的深度一致
  • 葉節點的指標為空
  • 節點中的資料key從左到右遞增排列

1. 樹節點結構:

在這裡需要說明下的是,BTree的結構裡每個節點包含了索引值和表記錄的資訊,我們可以按照Map集合這樣理解:key=索引,value=表記錄,如下圖所示:

2. 優點:

BTree的結構可以彌補紅黑樹的缺點,解決資料量過大時整棵樹的深度過長的問題。相同數量的資料只需要更少的層,相同深度的樹可以儲存更多的資料,查詢的效率自然會更高。

3. 缺點:

從上面得知,在查詢單條資料是非常快的。但如果範圍查的話,BTree結構每次都要從根節點查詢一遍,效率會有所降低,因此在實際應用中採用的是另一種BTree的變種B+Tree(B+樹)。

(五) B+Tree(MySQL索引的真正儲存結構)

在介紹B+Tree之前,我們先來看下面兩個問題:

1. 為什麼要對BTree繼續做優化?

要解答這個疑問需要先了解BTree每個節點結構(上面已經說明)和MySQL資料庫它是如何讀取索引資料的,索引和表資料在不使用的時候是儲存在檔案中的,也就是磁碟,當我們執行查詢操作時會DBMS(資料庫管理系統)首先會先從記憶體中查詢,如果找到直接使用,如果找不到則從磁碟檔案中讀取;作業系統儲存資料的最小單位是頁(page),一頁假設是4K大小(由作業系統決定),對記憶體和磁碟讀取資料是按一頁的整數倍讀取的。

這裡我們假設資料庫一次IO操作就讀取1頁4K的資料,再假設圖中圈起來的元素就是一個大節點,內含多個小節點的索引和資料,其大小是10MB,那麼我們要從磁碟中讀取完整個大節點需要進行 10M / 4K = 2500次IO操作,這樣就可以看出如果大節點資料總量越大,需要執行的IO操作越多,花費的時間也越長,因此為了提高效能,資料庫會建議我們一個大節點只儲存一頁4K大小的資料,這裡的資料包含了索引和表記錄,另外我們還能計算出樹的度Degree應該設定成多大才合理:

Degree = 記憶體頁大小(4K) / 單個索引值位元組大小;

進一步分析,索引值的大小相對於整條記錄的大小是很小的,如果我們需要查詢的資料剛好是在最後,那麼前面遍歷過的節點中儲存的記錄資料是不是對我們來說是沒用的,它會佔用比索引大得多的空間,導致我們一個大節點裡能遍歷的索引數量大大減少,需要向下繼續遍歷的機率就更大,花費更多時間查詢,那麼有沒有辦法可以優化呢?看下一個個問題。

2. 相對於BTree,B+Tree做了哪些優化?

  • B+Tree儲存結構,只有葉子節點儲存資料

新的B+樹結構沒有在所有的節點裡儲存記錄資料,而是隻在最下層的葉子節點儲存,上層的所有非葉子節點只存放索引資訊,這樣的結構可以讓單個節點存放下更多索引值,增大度Degree的值,提高命中目標記錄的機率。

這種結構會在上層非葉子節點儲存一部分冗餘資料,但是這樣的缺點都是可以容忍的,因為冗餘的都是索引資料,不會對記憶體造成大的負擔。

  • 每個葉子節點都指向下一個葉子節點

這點優化有什麼用呢?我們直接看下面的B+Tree結構,如果我們進行範圍查詢where id > 4的記錄,我們只需要先找到id = 4的記錄後自然就能通過葉子節點間的指標方便地查詢出大於4的所有記錄。

三. 聯合索引底層儲存結構

感謝大家的閱讀,本文如有錯誤的地方,希望能私信我改正,共同進步!