『MySQL』揭開索引神祕面紗

MySQL知識梳理圖,一圖看完整篇文章: html

上一篇文章簡單的介紹了MySQL的執行流程,這一篇想詳細介紹一下索引,一直想全面搞懂索引,嘗試寫一篇關於它的博文。mysql

1.索引是什麼?

索引是什麼了,查閱了官方文檔。官方文檔寫了索引的做用和沒有索引會帶來全表掃描,很是費時間。 Indexes are used to find rows with specific column values quickly. Without an index, MySQL must begin with the first row and then read through the entire table to find the relevant rows. 簡單的說索引是提升查詢速度。這個很好理解,就像是之前的英文詞典,找單詞若是沒有前面目錄的話,效率很低,得全文找一遍。算法

2.索引實現原理

要搞清楚索引的實現原理,先看看索引的底層實現,MySQL索引大部分採用B-Tree實現,B-Tree又有B-樹和B+樹。還有一些使用Hash索引。本文主要介紹B-Tree(Balance Tree)。sql

2.1 二叉搜索樹

再說B-Tree以前,先簡單瞭解一下二叉搜索樹(Binary Search Trees)。 數據庫

理解二叉搜索樹,對於後面理解B-和B+樹頗有幫助,由於這2種有些特性跟二叉搜索樹很像。二叉搜索樹的特色是左孩子的值小於父親節點的值,父親節點的值小於右孩子的值,即按二叉樹的中序遍歷,恰好是一個按小到大排序的。二叉搜索樹的查找就可使用二分查找,若是要查找10,由於10比27小,因此往左孩子找,10<14,還在左孩子找。最壞的狀況下,查找的次數等於樹的高度。

2.2 B-樹

一般意義上說B-Tree,通常是指B-樹,也能夠叫平衡多路搜索樹,平衡的意思能夠區瞭解一下平衡二叉樹(它是一棵空樹或它的左右兩個子樹的高度差的絕對值不超過1,而且左右兩個子樹都是一棵平衡二叉樹。),多路的意思就是非葉子節點的孩子至少有2個。 B-Tree的特徵也是很是燒腦,查看了算法導論書籍,也是琢磨了好久。下圖爲算法導論書中一張圖,淺陰影部分爲查找字母R時檢查過的結點。 數據結構

下面算法導論書中對B-樹的特徵:

  1. 每一個結點x有以下屬性:
    1. x.n。它表示儲存在 x中的關鍵字的個數;
    2. x.key1,x.key2,...,x.keyn。它們表示x的n個關鍵字,以非降序存放,即x.key1≤x.key2≤...≤x.keyn;
    3. x.leaf。它是一個布爾值,若是x是葉結點,它爲TRUE;不然爲FALSE;
  2. x.c1,x.c2,...,x.cn+1。它們是指向本身孩子的指針。若是該結點是葉節點,則沒有這些屬性。
  3. 關鍵字x.keyi對存儲在各子樹中的關鍵字範圍進行分割,即知足:k1≤x.key1≤k2≤x.key2≤...≤x.keyn≤kn+1。其中,ki(i=1,2,....,n+1)表示任意一個儲存在以x.ci爲根的子樹中的關鍵字。
  4. 每一個葉結點具備相同的深度,即葉的高度h。
  5. 每一個結點所包含的關鍵的個數有上下界。用一個被稱爲最小度數的固定整數t(t≥2)來表示這些界:
    1. 下界:除了根結點之外的每一個結點必須至少有t−1個關鍵字。所以,除了根結點外的每一個內部結點至少有t個孩子。
    2. 上界:每一個結點至多包含2t−1個關鍵字。所以,一個內部結點至多可能有2t個孩子。當一個結點剛好有2t−1個關鍵字時,稱該結點爲滿的(full)。

是否是仍是一頭霧水,接下來根據上圖一一解釋一下這幾個特徵。post

第1點是說每個節點包括的信息:n表示結點中存儲關鍵字的個數,好比上圖上M的左孩子就存了2個關鍵字,D和H;x.key,說的是具體的關鍵字的信息,好比D,D實際是有2個部分組成,能夠理解爲一個map,{key: data},x.key廣義上就是表示這個map,包括了具體的key和存儲的數據data,一般說是一條記錄;x.leaf是說整個結點是不是葉子結點。性能

第2點表示若是不是葉子結點,每一個結點還有一個屬性,就是指向它n個孩子的指針,好比上圖中的DH結點,有3個孩子,則有3個指針指向本身的孩子。優化

第3點表示說每一個結點的關鍵字按小到大的順序依次排列,同時各個結點之間也知足上面提到的二叉搜索樹的特色,左孩子的值<父親節點的值<右孩子的值。ui

第4點是說每一個葉子結點高度同樣,看圖就能夠明白,這也是平衡二字的由來。

第5點說的每一個結點關鍵字的數量的限制,不可能每一個結點能夠無限存儲關鍵字。t是最小度數,須要理解這些,能夠谷歌一下度數和階數的定義,上圖是4階的B-Tree。上圖中t=2,則每一個內部結點能夠容許有二、三、4個孩子。孩子數範圍[t, 2t],每一個結點的關鍵字範圍[t-1, 2t-1]。這個要區分。

下面更加形象的給出4階的B-Tree。

因爲B-Tree的特性,在B-Tree中按key檢索數據的算法很是高效:首先從根節點進行二分查找,若是找到則返回對應節點的data,不然對相應區間的指針指向的節點遞歸進行查找,直到找到節點或找到null指針,前者查找成功,後者查找失敗。

2.3 B+樹

終於寫完了B-樹,B+樹實際上是B-樹變種。 與B-樹最大的區別是內部結點不存儲data,只存儲key。以下圖:

通常數據庫系統中使用的B+樹再上圖經典的基礎上再進行了優化,變成了帶順序訪問指針的B+樹, 以下圖。這樣就提升區間訪問的性能,例如若是要查詢key爲從18到49的全部數據記錄,當找到18後,只需順着節點和指針順序遍歷就能夠一次性訪問到全部數據節點,極大提到了區間查詢效率。

3. 爲何是B-Tree(B+)來實現數據庫索引

3.1 磁盤存取原理

數據導論書中開頭就是說: B樹是爲磁盤或其餘直接存取的輔助存儲設備而設計的一種平衡搜索樹。上面提到了輔助存儲設備,那咱們就來看看其中原理,到底由來是什麼? 計算機系統有主存和基於磁盤的輔存,主存一般就是咱們說的RAM,也就是內存,這裏不展開說它。索引文件自己很大,通常不會存在內存裏,所以索引每每是以文件的形式存儲在磁盤裏,因此索引檢索須要磁盤I/O操做。下圖爲一個典型的磁盤驅動器。

磁盤讀取數據靠的是磁盤的機械運動。每次磁盤讀取的時間有三部分:尋道時間、旋轉延遲、傳輸時間。尋道時間指的是磁臂移動到指定磁道所須要的時間,主流磁盤通常在5ms如下;旋轉延遲就是咱們常常據說的磁盤轉速,好比一個磁盤7200轉,表示每分鐘能轉7200次,也就是說1秒鐘能轉120次,旋轉延遲就是1/120/2 = 4.17ms;傳輸時間指的是從磁盤讀出或將數據寫入磁盤的時間,通常在零點幾毫秒,相對於前兩個時間能夠忽略。那麼訪問一次磁盤讀取數據的時間,即一次磁盤I/O操做的時間約9ms左右,這相對於主存存儲時間50ns高出5個數量級。看着還不錯的,可是一臺500 -MIPS的機器每秒能夠執行5億條指令,由於指令依靠的是電的性質,換句話說執行一次IO的時間能夠執行40萬條指令,數據庫動輒十萬百萬乃至千萬級數據,每次9毫秒的時間,顯然是個災難。

爲了縮短磁盤讀取的時間,計算機作了一些優化:磁盤預讀。磁盤預讀是基於局部性原理:當一個數據被用到時,其附近的數據也一般會立刻被使用。因此磁盤I/O操做時不光把當前磁盤地址的數據,而是把相鄰的數據也都讀取到內存緩衝區內,由於局部性原理告訴咱們,當計算機訪問一個地址的數據的時候,與其相鄰的數據也會很快被訪問到。

預讀的長度通常爲頁(page)的整倍數。頁是計算機管理存儲器的邏輯塊,硬件及操做系統每每將主存和磁盤存儲區分割爲連續的大小相等的塊,每一個存儲塊稱爲一頁(在許多操做系統中,頁得大小一般爲4k),主存和磁盤以頁爲單位交換數據。

說了那麼多,總結一下:

  • 文件很大,不可能所有存儲在內存中,故要存儲到磁盤上。
  • 索引的結構組織要儘可能減小查找過程當中磁盤I/O的存取次數,由於每次磁盤I/O消耗時間都是很是多的。
  • 局部性原理與磁盤預讀,預讀的長度通常爲頁(page)的整倍數。

3.2 B-/B+的查找性能

數據庫系統巧妙利用了磁盤預讀原理,將一個節點的大小設爲等於一個頁,這樣每一個節點只須要一次I/O就能夠徹底載入。B-樹也利用這一點,每次新建節點時,直接申請一個頁的空間,這樣就保證一個節點物理上也存儲在一個頁裏,加之計算機存儲分配都是按頁對齊的,就實現了一次磁盤I/O就讀取了一頁的數據。下面是B-樹的示例圖:

根據B-Tree的定義,可知檢索一次最多須要訪問h個節點(h個樹的高度)。B-Tree中一次檢索最多須要h-1次I/O(根節點常駐內存),漸進複雜度爲O(h)=O(logdN)。通常實際應用中,出度d是很是大的數字,一般超過100,所以h很是小(一般不超過3)。因此B-Tree做爲索引效率是很是高,相比平衡二叉樹、紅黑樹要高不少,由於這些樹的h通常都比較深。

下面附一張B+樹的直觀圖:

B+樹比B-樹更加適合做爲磁盤的索引數據結構,緣由是B+樹的內部結點不存儲data,內部結點的出度d越大,那麼漸進複雜度越小。出度d的上限取決於節點內key和data的大小: dmax=floor(pagesize/(keysize+datasize+pointsize))

通常3層B+樹能夠存儲上百萬的數據,也就是讀取上百萬的數據,只須要3次磁盤I/O,可見這效率,大大提高了。若是沒有索引,那每次查詢一次數據項,都須要一次I/O,幾百萬次,可怕。

4. 不一樣引擎的索引實現原理

4.1 MyISAM索引實現

MyISAM的索引採用B+樹實現,MyISAM的索引和數據時分開的,葉子節點data存取的是數據的地址。以下主鍵索引的示例圖:

由圖能夠看出,要根據索引找到數據,先根據索引找到葉子節點,再根據葉子節點找到數據的地址,而後再根據數據地址取出數據。

MyIASM的輔助索引的實現與主鍵索引沒有區別,以下圖:

單獨出來講,是爲了待會跟InnoDB做區分。

4.2 InnoDB索引實現

InnoDB,在實際項目接觸是很是多的,索引的實現也是使用B+樹,可是實現原理跟MyISAM不一樣。 第一個區別是InnoDB的數據文件自己就是索引文件。MyISAM索引文件和數據文件是分離的,索引文件僅保存數據記錄的地址。而在InnoDB中,表數據文件自己就是按B+Tree組織的一個索引結構,這棵樹的葉節點data域保存了完整的數據記錄。這個索引的key是數據表的主鍵,所以InnoDB表數據文件自己就是主索引。以下圖:

這種索引叫作彙集索引。由於InnoDB的數據文件自己要按主鍵彙集,因此InnoDB要求表必須有主鍵(MyISAM能夠沒有),若是沒有顯式指定,則MySQL系統會自動選擇一個能夠惟一標識數據記錄的列做爲主鍵,若是不存在這種列,則MySQL自動爲InnoDB表生成一個隱含字段做爲主鍵,這個字段長度爲6個字節,類型爲長整形。

第二個區別就是InnoDB的輔助索引data域存儲相應記錄主鍵的值而不是地址。換句話說,InnoDB的全部輔助索引都引用主鍵做爲data域。以下圖所示:

4.3 意義

瞭解不一樣的引擎的實現原理,對於咱們平常作數據庫的設計是很是有幫助的。

  1. InnoDB輔助索引搜索須要檢索兩遍索引:首先檢索輔助索引得到主鍵,而後用主鍵到主索引中檢索得到記錄,從而可以明白爲何不建議使用過長的字段做爲主鍵,由於全部輔助索引都引用主索引,過長的主索引會令輔助索引變得過大。
  2. 不建議用非單調的字段做爲InnoDB的主鍵,由於InnoDB數據文件自己是一顆B+Tree,非單調的主鍵會形成在插入新記錄時,數據文件爲了維持B+Tree的特性而頻繁的分裂調整,十分低效,因此通常使用自增字段做爲主鍵。

這一篇先到這裏吧,下一篇總結索引的優化策略。

參考:

1. http://blog.codinglabs.org/articles/theory-of-mysql-index.html
 2. 算法導論(第三版)
複製代碼

更多精彩文章,請關注公衆號:「 天澄技術雜談 」

相關文章
相關標籤/搜索