MySql 索引底層數據結構和算法

前言

文章基於MySql 5.7.24分析,部分圖片源於網絡,是MySql索引學習筆記。

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

這裏只關注BTree索引,這是平時實戰中使用最多的索引。

一 MySql索引概要

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

索引就好比書的目錄,你可以通過目錄直接找到對應的目標,而不需要從第一頁翻起

逐頁查找,運氣不好,就得從頭翻到尾。

簡單來說,索引是一種存儲在文件裏面排好序的數據結構。

MyISAM存儲引擎表test01,在MySql的data裏面有三個文件:

test01.frm:存儲表結構;

test01.MYD:存儲數據;

test01.MYI:存儲索引;

InnoDB存儲引擎表test02,在MySql的data裏面有二個文件:

test02.frm:存儲表結構;

test02.ibd:存儲數據和索引;

二 索引基礎數據結構和算法

爲什麼使用B+Tree作爲索引的數據結構,爲什麼不用二叉樹、紅黑樹、B-Tree等等?

索引提高獲取數據效率的本質是什麼?

1、磁盤的讀寫原理

1.1 磁盤立體示意圖

一個磁盤由大小相同且同軸的圓形盤片組成,這個旋轉軸稱爲盤片主軸。而所有盤片之間是絕對平行的,

在每個盤片的存儲面上都有一個磁頭,磁頭與盤片之間的距離非常小。所有的磁頭連在一個磁頭控制器上,

由磁頭控制器負責各個磁頭的運動。磁頭可沿盤片的半徑方向動作(實際是斜切向運動),

每個磁頭同一時刻也必須是同軸的,即從正上方向下看,所有磁頭任何時候都是重疊的(不過目前已經有

多磁頭獨立技術,可不受此限制)。而盤片以每分鐘數千轉到上萬轉的速度在高速旋轉,這樣磁頭就能

對盤片上的指定位置進行數據的讀寫操作。

1.2 俯視示意圖

磁盤示意圖和磁盤俯視圖。

    

 

盤片被劃分成一系列同心環,圓心是盤片中心,每個同心環叫做一個磁道,所有半徑相同的磁道組成一個柱面。

磁道被沿半徑線劃分成一個個小的段,每個段叫做一個扇區,每個扇區是磁盤的最小存儲單元。爲了簡單起見,

我們下面假設磁盤只有一個盤片和一個磁頭。

當需要從磁盤讀取數據時,系統會將數據邏輯地址傳給磁盤,磁盤的控制電路按照尋址邏輯將邏輯地址翻譯成物理地址,

即確定要讀的數據在哪個磁道,哪個扇區。爲了讀取這個扇區的數據,需要將磁頭放到這個扇區上方,爲了實現這一點,

磁頭需要移動對準相應磁道,這個過程叫做尋道,所耗費時間叫做尋道時間(速度慢,費時),然後磁盤旋轉將目標扇區

旋轉到磁頭下,這個過程耗費的時間叫做旋轉時間(速度較快)

MySql數據存儲在磁盤上,一般來說需要經過尋道和旋轉,將磁頭對應這個數據扇區上方,

才能從磁盤上讀取這條數據。如果我們能夠通過某種方式明確的告訴計算機去磁盤的哪裏存取數據,

而不是每次都讓計算機去磁盤上“瞎忙”,地毯式搜索數據,存取數據的效率將會高很多。

而索引,正是MySql用來解決減少磁盤I/O,提高存取效率的手段。所以,提高MySql存儲效率

的本質就是想辦法減少磁盤I/O次數,儘快從磁盤上獲取數據。

1.3 看一個例子,假設用一個樹狀結構優化查找效率

假設表上有7行兩列數據,最左側爲物理地址,如果查找Col2的某一行數據,需要逐個遍歷獲取對應數據。

比如,查找23這行數據,需要從第一行開始查找,逐行查找,直到找到位置,總的要查找7次。

(注意邏輯上相鄰的記錄在磁盤上也並不是一定物理相鄰的,有可能查找更多次數,效率更低)。

爲了加快Col2查找,可維護最右側的樹型結構,每個節點分別包含索引鍵值和一個指向對應數據記錄物理地址的指針。

比如,查找23這行數據,3次就能找到23這個索引鍵值,通過對應的物理地址指針,直接就能獲取23這行數據,

獲取數據效率更高。

這就是索引的目標,減少磁盤I/O次數,提高MySql獲取數據的效率

下面討論MySql爲什麼選擇我們最常打交道的B+Tree實現BTree索引。

2、二叉樹

MySql爲什麼不選擇二叉樹作爲索引數據結構?

如果使用二叉樹作爲索引數據結構,以Col1建立索引,這裏Col1從1到7是順序的,建立出來數據結構是這樣的:

在極端情況下,如果某個列數據順序增長,建立出來的索引結構也是順序存儲的,

跟從表裏面順序查找的效率是一樣的,會失去索引存在的意義,這就是不用二叉樹作爲索引數據結構的原因。

3、紅黑樹

MySql爲什麼不選擇紅黑樹作爲索引數據結構?

如果使用紅黑樹作爲索引數據結構,以Col1建立索引,這裏Col1從1到7是順序的,建立出來數據結構是這樣的:

從紅黑樹結構可以看到,整棵樹是失衡的,如果查找7的時候,會經歷四次查找,

同時,每一個節點只能有兩個子節點,如果數據量特別大的時候,樹的深度非常大,

整棵樹查找效率非常低,也不是MySql索引數據結構的最佳選擇。

4、Hash

MySql用Hash作爲索引數據結構的優缺點,爲什麼Hash用得哪麼少?

如果我們給Col1每行數據都算一個Hash值,查找的時候直接通過Hash值查找到對應的數據,

這樣非常的快,磁盤能直接就定位到數據。但是用Hash有個弊端,如果要進行範圍查找,

Hash處理不了範圍查找,而實際SQL中,範圍查找也是非常常見的。索引Hash在個別值查找

時效率是不錯的,比如身份證字段建一個Hash索引,範圍查找用得比較少的,效率會比較不錯,

千萬不要在使用範圍查找的列建立Hash索引。所以,在實際中使用Hash索引比較少。

5、B-Tree樹

綜合上面的數據結構,MySql自然會想到要用一種平衡的結構去解決索引存儲問題。

自然會想到B-Tree(不要讀成B減Tree,要不然就跟回老家把親戚輩分叫錯了一樣尷尬,它是B樹,balance)。

在分析B-Tree之前,還需要了解局部性原理與磁盤預讀

由於存儲介質的特性,磁盤本身存取就比主存慢很多,再加上機械運動耗費,磁盤的存取速度往往是主存的幾百分之一,

因此爲了提高效率,要儘量減少磁盤I/O。爲了達到這個目的,磁盤往往不是嚴格按需讀取,而是每次都會預讀,

即使只需要一個字節,磁盤也會從這個位置開始,順序向後讀取一定長度的數據放入內存。這樣做的理論依據是

計算機科學中著名的局部性原理

當一個數據被用到時,其附近的數據也通常會馬上被使用。程序運行期間所需要的數據通常比較集中。

由於磁盤順序讀取的效率很高(不需要尋道時間,只需很少的旋轉時間),因此對於具有局部性的程序來說,

預讀可以提高I/O效率。

預讀的長度一般爲頁(page)的整倍數。頁是計算機管理存儲器的邏輯塊,硬件及操作系統往往將主存和磁盤存儲區分

割爲連續的大小相等的塊,每個存儲塊稱爲一頁(在許多操作系統中,頁得大小通常爲4k),主存和磁盤以頁爲單位交換數

據。當程序要讀取的數據不在主存中時,會觸發一個缺頁異常,此時系統會向磁盤發出讀盤信號,磁盤會找到數據的起始

位置並向後連續讀取一頁或幾頁載入內存中,然後異常返回,程序繼續運行。

假設數據庫有兩列數據,如下:

如果設定B-Tree的Degree爲4,將Col1存儲到B-Tree,結構如下:

是不是感覺很清爽,如果要查找7,最多兩次就搞定,效率很高。

B-Tree特性:

如下圖,是從網上找的一個B-Tree簡要結構,並不是磁盤存儲的真正結構,是抽象出來的,

用它來說明B-Tree的特性比較直觀。

  • 度(Degree)-節點的數據存儲個數;
  • 葉節點具有相同的深度;
  • 葉節點的指針爲空;
  • 節點中的數據key從左到右遞增排列;

15,56,77等等爲索引大節點中的小節點,每一個小節點均爲key-value結構,key存儲鍵值,value存儲對應的行數據。

當我們查詢數據時,cpu從磁盤把部分索引文件內容加載到內存,在內存查找到目標值,然後有目標的去磁盤拿

我們的目標數據,減少磁盤I/O操作。然而使用B-Tree建立索引文件後,每一個節點上都有數據data,

而每次從磁盤加載到內存的數據是有限制的,一般爲一頁或幾頁(一般一頁的大小爲4k),如果一個節點數據很大的時候,

我們需要很多次磁盤I/O操作才能把一個節點載入到內存,效率很低,就失去了索引的意義。

所以,這就是MySql不使用B-Tree作爲索引數據結構。

6、B+Tree樹

經過以上分析,終於見到MySql索引數據結構B+Tree,B+Tree是B-Tree的一個變種。B+Tree特性:

  • 非葉子節點不存儲data,只存儲key,可以增大度;
  • 葉子節點存儲文件指針或數據,但是MySql不同引擎存儲的內容不一樣;
  • 順序訪問指針,提高區間訪問的性能;

爲什麼要使用B+Tree作爲索引的數據結構?

一般使用磁盤I/O次數評價索引結構的優劣。

B+Tree在非葉子節點上不存儲數據,只存索引的鍵值,這樣能保證每一個節點上數據非常小,

MySql根據B+Tree的特性和局部性原理,將B+Tree節點的大小設爲等於一個頁,每次新建節點直接申請

一個頁的空間,這樣就保證一個節點物理上也存儲在一個頁裏,就實現了一個節點的載入只需一次I/O

大大減少磁盤I/O次數,提高獲取數據的效率。同時B+Tree的度d一般會超過100,因此h非常小(一般爲3到5之間),

只需要幾次I/O操作就能把索引檢索完,效率非常高。

三 MyISAM引擎索引實現(非聚集)

MyISAM存儲引擎上的索引是非聚集索引,因爲索引文件和數據文件是分離存儲的。

MyISAM存儲引擎和InnoDB存儲引擎在葉子節點上存儲的數據不一樣。

MyISAM存儲引擎表test01,在MySql的data裏面有三個文件:

test01.frm:存儲表結構;

test01.MYD:存儲數據;

test01.MYI:存儲索引;

1、MyISAM主鍵索引

MyISAM主鍵索引的數據結構如下:

比如以主鍵Col1建立索引,在葉子節點上存儲的是文件指針,如果我們要查找30這行數據,

則先在索引文件(MYI)根據B+Tree查找到30這條數據的文件指針,然後通過文件指針

直接從磁盤數據文件(MYD)中查找到對應的數據。如果是範圍查找,因爲索引已經排好序了,

直接從磁盤讀取對應範圍的數據。

2、MyISAM非主鍵索引

MyISAM存儲引擎的非主鍵索引存儲結構與其主鍵索引的存儲結構相同,查找方式一樣。

四 InnoDB引擎索引實現(聚集)

InnoDB是聚集索引,因爲索引文件和數據文件聚集在一起,合併在一起了。

InnoDB存儲引擎表test02,在MySql的data裏面有二個文件:

test02.frm:存儲表結構;

test02.ibd:存儲數據和索引;

數據文件本身就是索引文件,表數據文件本身就是按B+Tree組織的一個索引結構文件。

1、InnoDB主鍵索引

InnoDB引擎主鍵索引在葉子節點存儲的不是文件指針,與MyISAM存儲引擎在葉子節點存儲文件指針不同,

因爲索引文件和數據文件是合在一起的,所以,在葉子節點存儲的是對應索引鍵值的數據。

我們只要通過索引遍歷就能查找到數據,相比MyISAM引擎,能夠減少一次通過文件指針

從數據文件(ibd)獲取數據的一次磁盤I/O。

爲什麼InnoDB表必須有主鍵,並且推薦使用整型的自增主鍵?

MySql InnoDB引擎會基於主鍵建立B+Tree數據結構,如果沒有主鍵,

它會選擇唯一數據列(比如唯一索引列)建立B+Tree數據結構。

如果主鍵和唯一鍵都沒有,它就隨便給你搞一列建立B+Tree數據結構。

所以,MySql推薦使用整型的自增主鍵,因爲整型比較大小查找比字符串快,

整型一比較就知道大小,字符串需要根據ASCII碼進行比較,相對較慢,

同時,在插入數據的時候,使用字符串移動次數比較頻繁,並且使用字符串也比較佔用存儲空間,

所以從空間和時間上推薦使用整型自增主鍵。

2、InnoDB非主鍵索引

InnoDB非主鍵索引在葉子節點存儲的是該行數據的主鍵值。

通過索引結構找到對應數據的主鍵,然後通過主鍵物理地址去磁盤ibd文件

直接獲取數據。

爲什麼非主鍵索引結構葉子節點存儲的是主鍵值?

1)一致性,爲了主鍵索引和非主鍵索引數據一致性。如果非主鍵索引上面存儲的是數據,

哪麼如果數據發生變動,需要修改主鍵索引上的數據,同時也需要維護非主鍵索引的數據,

從事務的角度來說,這樣就需要採取措施保證主鍵索引和非主鍵索引上的數據要一致,

大大增加了處理數據的複雜性,可能會產生數據不同不,哪麼使用不同索引查找到的數據就不一致了。

所以在非主鍵索引上存儲主鍵值,如果數據發生改動,只需要修改主鍵索引上的數據,

而非主鍵索引存儲的主鍵值不需要變動。本質上就是保證主鍵索引和非主鍵索引都是

從同一個地方拿數據,保證數據的一致性。

2)節省存儲空間

非主鍵索引葉子節點存儲主鍵的另外一個原因就是爲了節省存儲空間,能把數據歸類在一個地方

供大家調用,何必要把數據到處散落呢。

五 聯合索引實現原理

單值索引只是聯合索引字段爲1個的情況,聯合索引由多個字段構成。

比如通過表裏面三個字段建立聯合索引。

聯合索引查找分析:

1)如果第一個字段是101,直接就能比較出大小,無需第二個字段比較。

2)如果第一個字段101一樣,則通過第二個字段比較大小。

3)如果第一個103和第二個html5一樣,則通過第三個字段比較大小,

如此依次類推,就是聯合索引查找方式。

六 最左前綴實現原理

比如有通過三個字段seqNo,name, date構建聯合索引,如下:

最左前綴就是多列字段構建的聯合索引,最左邊的字段優先比較

最左前綴原理有很多使用原則,比如:全值匹配法則,最左前綴法則等等。

七 總結

1、MySql BTree索引使用B+Tree數據結構;

2、MySql不同存儲引擎在B+Tree葉子節點存儲的數據不一樣,

MyISAM主鍵索引和非主鍵索引存儲文件指針,InnoDB主鍵索引存儲數據,InnoDB非主鍵索引存儲主鍵值;

3、聯合索引由多個字段聯合構建,最左前綴原理查找時最左邊的字段優先比較。

索引優化博大精深,這篇主要分析BTree底層數據結構,下篇結合索引數據結構去分析、使用和優化。

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