一文完全搞懂MySQL索引

前言

MyISAM和InnoDB是MySQL最經常使用的兩個存儲引擎,本文將進行詳盡的介紹和對比。對於MySQL其他幾種存儲引擎,請讀者自行搜索學習。mysql

本文會圖解兩種引擎的索引結構區別,而後講解索引的原理,理解本文內容,就可以理解索引優化的各類原則的背後緣由。算法

限於篇幅,本篇沒有介紹的知識,會在後續博客將逐一講解。例如:MySQL引擎的鎖機制、多列索引的生效規則、索引優化等主題。sql

下面SQL在本篇介紹引擎的結構區別時使用的表結構,便於讀者更好理解。數據庫

CREATE TABLE `user` (
  `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '惟一碼',
  `age` int(5) NOT NULL COMMENT '年齡',
  `name` varchar(5) NOT NULL COMMENT '名字',
  PRIMARY KEY (`id`),
  KEY `name` (`name`)
) ENGINE=InnoDB AUTO_INCREMENT=92 DEFAULT CHARSET=utf8mb4;
複製代碼

B-樹、B樹和B-tree是同一個數據結構,只不過英語翻譯過來以後,有些人誤解了覺得是多種樹。因此好多講解樹的數據結構的博客徹底是誤導初學者。。。請讀者認真分辨。緩存

MyISAM和InnoDB的索引均採用B+樹數據結構,因此接下來先介紹一下B樹與B+樹。安全

B樹與B+樹

B樹

B樹是一種多路搜索樹。bash

  1. 定義任意非葉子結點最多隻有M個兒子,且M>2。
  2. 根結點的兒子數爲[2, M]。
  3. 除根結點之外的非葉子結點的兒子數爲[M/2, M]。
  4. 每一個結點存放至少M/2-1(取上整)和至多M-1個關鍵字;(至少2個關鍵字)。
  5. 非葉子結點的關鍵字個數=指向兒子的指針個數-1。
  6. 非葉子結點的關鍵字:K[1], K[2], …, K[M-1],且K[i] <= K[i+1]。
  7. 非葉子結點的指針:P[1], P[2], …,P[M](其中P[1]指向關鍵字小於K[1]的子樹,P[M]指向關鍵字大於K[M-1]的子樹,其它P[i]指向關鍵字屬於(K[i-1], K[i])的子樹)。
  8. 全部葉子結點位於同一層。

下圖是一個M=4階的B樹。數據結構

B樹的搜索,從根結點開始,對結點內的關鍵字(有序)序列進行二分查找,若是命中則結束,不然進入查詢關鍵字所屬範圍的兒子結點;重複,直到所對應的是葉子結點。併發

查找文件29的過程:nosql

  1. 根據根結點指針找到文件目錄的根磁盤塊1,將其中的信息導入內存。(磁盤IO操做1次)
  2. 此時內存中有兩個文件名17,35和三個存儲其餘磁盤頁面地址的數據。根據算法咱們發現17<29<35,所以咱們找到指針p2。
  3. 根據p2指針,咱們定位到磁盤塊3,並將其中的信息導入內存。(磁盤IO操做2次)
  4. 此時內存中有兩個文件名26,30和三個存儲其餘磁盤頁面地址的數據。根據算法咱們發現26<29<30,所以咱們找到指針p2。
  5. 根據p2指針,咱們定位到磁盤塊8,並將其中的信息導入內存。(磁盤IO操做3次)
  6. 此時內存中有兩個文件名28,29。根據算法咱們查找到文件29,並定位了該文件內存的磁盤地址。

下面的動畫是4階B樹插入的過程。

B樹的特性:

  1. 關鍵字分佈在整顆樹的全部節點。
  2. 任何一個關鍵字出現且只出如今一個結點中。
  3. 搜索有可能在非葉子結點結束。
  4. 其搜索性能等價於在關鍵字全集內作一次二分查找。

B+樹

下圖是一個M=3階的B+樹。

通常在數據庫系統或文件系統中使用的B+Tree結構都在經典B+Tree的基礎上進行了優化,增長了順序訪問指針。

B+樹是B樹的一種變形樹,總結起來,數據庫索引的B+樹與B樹的差別在於:

  1. 非葉子結點的子樹指針與關鍵字個數相同。
  2. 非葉子結點的子樹指針P[i],指向關鍵字值屬於[K[i],K[i+1])的子樹(注意,區間是前閉後開)。
  3. 爲全部葉子結點增長一個鏈指針。
  4. 全部關鍵字都在葉子結點出現。

B+樹的特性:

  1. 全部關鍵字都出如今葉子結點的鏈表中,且鏈表中的關鍵字是有序的。
  2. 搜索只在葉子結點命中。
  3. 非葉子結點至關因而葉子結點的索引,葉子結點是存儲關鍵字數據的數據層。

B-/+樹作索引的緣由

解釋這個問題以前,須要瞭解一些基礎知識。

局部性原理與磁盤預讀

因爲存儲介質的特性,磁盤自己存取就比主存慢不少,再加上機械運動耗費,磁盤的存取速度每每是主存的幾百分之一,所以爲了提升效率,要儘可能減小磁盤I/O。爲了達到這個目的,磁盤每每不是嚴格按需讀取,而是每次都會預讀,即便只須要一個字節,磁盤也會從這個位置開始,順序向後讀取必定長度的數據放入內存。這樣作的理論依據是計算機科學中著名的局部性原理:

當一個數據被用到時,其附近的數據也一般會立刻被使用——程序運行期間所須要的數據一般比較集中。

因爲磁盤順序讀取的效率很高(不須要尋道時間,只需不多的旋轉時間),所以對於具備局部性的程序來講,預讀能夠提升I/O效率。

預讀的長度通常爲頁的整倍數。頁是計算機管理存儲器的邏輯塊,硬件及操做系統每每將主存和磁盤存儲區分割爲連續的大小相等的塊,每一個存儲塊稱爲一頁(在許多操做系統中,頁得大小一般爲4k),主存和磁盤以頁爲單位交換數據。當程序要讀取的數據不在主存中時,會觸發一個缺頁異常,此時系統會向磁盤發出讀盤信號,磁盤會找到數據的起始位置並向後連續讀取一頁或幾頁載入內存中,而後異常返回,程序繼續運行。

B-/+樹作索引的緣由分析

通常來講,磁盤I/O次數能夠用於評價索引結構的優劣。在B-Tree中查找,可知檢索一次最多須要訪問h個節點(上文舉例查找文件29的過程)。數據庫系統的設計者巧妙利用了磁盤預讀原理,將一個節點的大小設爲等於一個頁,這樣每一個節點只須要一次I/O就能夠徹底載入。

爲了達到這個目的,在實際實現中,B樹還使用以下技巧:

  1. 每次新建節點時,直接申請一個頁的空間,這樣就保證一個節點物理上也存儲在一個頁裏,加之計算機存儲分配都是按頁對齊的,就實現了一個節點只需一次I/O。
  2. B樹中一次檢索最多須要h-1次I/O(根節點常駐內存)。通常實際應用中,出度d(樹的分叉數)是很是大的數字,一般超過100;h很是小,一般不超過3。

綜上所述,用B樹做爲索引結構效率是很是高的。

紅黑樹或者平衡二叉樹的其餘樹結構,

  1. h明顯要深的多,執行效率低。
  2. 邏輯上很近的節點(父子)物理上可能很遠,沒法利用局部性,
  3. 每一個節點存儲的數據量過小了,對磁盤空間形成浪費,帶來頻繁的IO操做。

因此其餘樹結構的效率明顯比B樹差不少。

相對B樹,B+樹作索引的優點

  1. B+樹的磁盤讀寫代價更低:B+樹的內部節點並無指向關鍵字具體信息的指針,所以其內部節點相對B樹更小,若是把全部同一內部節點的關鍵字存放在同一盤塊中,那麼盤塊所能容納的關鍵字數量也越多,一次性讀入內存的須要查找的關鍵字也就越多,相對IO讀寫次數就下降了。
  2. B+樹的查詢效率更加穩定:因爲全部數據都存於葉子節點。全部關鍵字查詢的路徑長度相同,每個數據的查詢效率至關。
  3. B樹在提升了IO性能的同時並無解決元素遍歷的我效率低下的問題,正是爲了解決這個問題,B+樹應用而生。B+樹只須要去遍歷葉子節點就能夠實現整棵樹的遍歷。

筆者認爲第三條緣由纔是MySQL使用B+樹而不是B樹作索引的主要緣由,畢竟MongoDB的索引是B樹,因此兩種數據結構並無絕對的好壞,要看實際的業務需求。

MyISAM

磁盤存儲

MyISAM在磁盤存儲上有三個文件,每一個文件名以表名開頭,擴展名指出文件類型。

  1. .frm:用於存儲表的定義。
  2. .MYD:用於存放數據。
  3. .MYI:用於存放表索引。

索引

主鍵索引

MyISAM引擎使用B+樹做爲索引結果,葉節點的data域存放的是數據記錄的地址。

MyISAM索引文件和數據文件是分離的,索引文件僅保存記錄所在頁的指針(物理位置),經過這些地址來讀取頁,進而讀取被索引的行。

樹中葉子保存的是對應行的物理位置。經過該值,存儲引擎能順利地進行回表查詢,獲得一行完整記錄。同時,每一個葉子頁也保存了指向下一個葉子頁的指針。從而方便葉子節點的範圍遍歷。

輔助索引

在MyISAM中,主鍵索引和輔助索引在結構上沒有任何區別,只是主鍵索引要求key是惟一的,而輔助索引的key能夠重複。

Innodb

MySQL5.5開始支持InnoDB引擎,並將其做爲默認數據庫引擎。

磁盤存儲

Innodb有兩種存儲方式,共享表空間存儲和多表空間存儲。

Innodb只有表結構文件和數據文件。

表結構文件和MyISAM同樣,以表名開頭,擴展名是.frm。

數據文件與存儲方式有關:

  • 若是使用共享表空間,那麼全部表的數據文件和索引文件都保存在一個表空間裏,一個表空間能夠有多個文件,經過innodb_data_file_path和innodb_data_home_dir參數設置共享表空間的位置和名字,通常共享表空間的名字叫ibdata1-n。
  • 若是使用多表空間,那麼每一個表都有一個表空間文件用於存儲每一個表的數據和索引,文件名以表名開頭,以.ibd爲擴展名。

索引

主鍵索引

Innodb主鍵索引中,既存儲了主鍵值,又存儲了行數據。

輔助索引

對於輔助索引,InnoDB採用的方式是在葉子頁中保存主鍵值,經過這個主鍵值來回表(上圖)查詢到一條完整記錄,所以按輔助索引檢索實際上進行了二次查詢,效率確定是沒有按照主鍵檢索高的。

Innodb與MyISAM的區別

1. 存儲結構

MyISAM存儲表分爲三個文件frm(表結構)、MYD(表數據)、MYI(表索引),而Innodb如上文所說,根據存儲方式不一樣,存儲結構不一樣。

2. 事務支持

MyISAM不支持事務,而Innodb支持事務,具備事務、回滾和恢復的事務安全。

3. 外鍵和主鍵

MyISAM不支持外鍵,而Innodb支持外鍵。MyISAM容許沒有主鍵,可是Innodb必須有主鍵,若未指定主鍵,會自動生成長度爲6字節的主鍵。

4. 鎖

MyISAM只支持表級鎖,而Innodb支持行級鎖,具備比較好的併發性能,可是行級鎖只有在where子句是對主鍵篩選才生效,非主鍵where會鎖全表

5. 索引

MyISAM使用B+樹做爲索引結構,葉節點保存的是存儲數據的地址,主鍵索引key值惟一,輔助索引key能夠重複,兩者在結構上相同。Innodb也是用B+樹做爲索引結構,數據表自己就是按照b+樹組織,葉節點key值爲數據記錄的主鍵,data域爲完整的數據記錄,輔助索引data域保存的是數據記錄的主鍵。

FAQ

MongoDB的索引爲何選擇B樹,而Mysql的索引是B+樹

MongoDB不是傳統的關係性數據庫,而是以Json格式做爲存儲的nosql,目的就是高性能,高可用,易擴展。首先它擺脫了關係模型,因此範圍查詢和遍歷查詢的需求就沒那麼強烈了,其次Mysql因爲使用B+樹,數據都在葉節點上,每次查詢都須要訪問到葉節點,而MongoDB使用B-樹,全部節點都有Data域,只要找到指定索引就能夠進行訪問。

整體來講,Mysql選用B+樹和MongoDB選用B-樹仍是以本身的需求來選擇的。

索引有關的名詞解釋

普通索引

用表中的普通列構建的索引,沒有任何限制

惟一索引

惟一索引列的值必須惟一,但容許有空值。若是是組合索引,則列值的組合必須惟一。

主鍵索引

根據主鍵創建索引,不容許重複,不容許空值;

全文索引

僅可用於MyISAM表,針對較大的數據,生成全文索引很是的消耗時間和空間(在生成FULLTEXT索引時,會爲文本生成一份單詞的清單,在索引時及根據這個單詞的清單來索引)。

組合索引

又叫聯合索引。用多個列組合構建的索引,這多個列中的值不容許有空值。能夠在建立表的時候指定,也能夠修改表結構。

ALTER TABLE 'table_name' ADD INDEX index_name('col1','col2','col3');

爲了更多的提升mysql效率可創建組合索引,遵循」最左前綴「原則。建立複合索引時應該將最經常使用(頻率)做限制條件的列放在最左邊,依次遞減。示例的組合索引至關於創建了col1,col1col2,col1col2col3三個索引,而col2或者col3是不能使用索引的。

最左前綴規則

假設聯合索引由列(a,b,c)組成,則一下順序知足最左前綴規則:a、ab、abc;selece、where、order by 、group by均可以匹配最左前綴。其它狀況都不知足最左前綴規則就不會用到聯合索引。

彙集索引

定義:數據行的物理順序與列值(通常是主鍵的那一列)的邏輯順序相同,一個表中只能擁有一個彙集索引。

若是定義了主鍵,Innodb會選擇主鍵做爲彙集索引;若是沒有定義主鍵,Innodb會選擇不包含NULL值的惟一索引做爲彙集索引;若是也沒有這樣的惟一索引列,Innodb會選擇內置6字節長的rowID做爲隱含的彙集索引,這裏的RowId會隨着記錄的寫入而主鍵自增,可是它是不可引用和查看的,是數據庫引擎內部的使用。

若是咱們使用自增主鍵,那麼每次插入的新紀錄都在原先記錄的尾部按照順序,添加到當前節點的索引後面,當一頁快寫滿的時候,就會開闢一個新的頁。數據記錄自己就存與主索引的葉子節點上,B+tree的樹。這就要求每個葉子節點內的各條數據記錄按主鍵順序存放,所以每當有一條新的記錄插入的時候,MYSQL會根據其主鍵將其插入到合適的節點和位置上,若是頁面達到裝載因子(INNODB默認爲15/16),則開闢新的頁面(節點)

若是使用非自增主鍵(若是身份證號或學號等),因爲每次插入主鍵的值近似於隨機,所以每次新紀錄都要被插到現有索引頁得中間某個位置,此時MySQL不得不爲了將新記錄插到合適位置而移動數據,甚至目標頁面可能已經被回寫到磁盤上而從緩存中清掉,此時又要從磁盤上讀回來,這增長了不少開銷,同時頻繁的移動、分頁操做形成了大量的碎片,獲得了不夠緊湊的索引結構,後續不得不經過OPTIMIZE TABLE來重建表並優化填充頁面。

非彙集索引

定義:該索引中索引的邏輯順序與磁盤上行的物理存儲順序不一樣,一個表中能夠擁有多個非彙集索引。

除了InnoDB的主鍵索引,在mysql中的其餘索引形式都是非彙集索引。

覆蓋索引

指從輔助索引中就能獲取到須要的記錄,而不須要查找主鍵索引中的記錄。使用覆蓋索引的一個好處是由於輔助索引不包括一條記錄的整行信息,因此數據量較彙集索引要少,能夠減小大量io操做。

覆蓋查詢失效

  1. select選擇的字段中含有不在索引中的字段 ,即索引沒有覆蓋所有的列。
  2. where條件中不能含有對索引進行like的操做。
相關文章
相關標籤/搜索