MySQL索引結構原理分析

咱們在學習MySQL的時候常常會聽到索引這個詞,大概也知道這是什麼,可是深究下去又說不出什麼道道來。下面將會比較全面的介紹一下關於索引!html

1 索引是什麼?

這裏用百度百科的一句話來講,在關係數據庫中,索引是一種單獨的、物理的對數據庫表中一列或多列的值進行排序的一種存儲結構,它是某個表中一列或若干列值的集合和相應的指向表中物理標識這些值的數據頁的邏輯指針清單。mysql

簡單來講,索引就是咱們一本書的目錄,經過目錄咱們才能更快在一本書中查找到咱們所要看的內容。一樣的,經過索引咱們才能在數據庫中查找到咱們的數據!git

2 沒使用索引的MySQL

咱們知道索引能夠加快咱們的查找,因此這裏經過沒有使用索引的查找能夠更加地讓咱們認識到使用索引的好處。github

咱們的MySQL基本的頁存儲結構是頁,也就是咱們的數據記錄都在頁裏面。算法

當咱們插入一條記錄的時候就會存儲在咱們的數據頁中的存放行記錄的位置,並在咱們的Page Directory頁目錄那裏生成主鍵的信息。咱們的數據頁中記錄又能夠組成一個單鏈表,每插入一條數據就會在尾節點那裏添加上。sql

當咱們經過主鍵查找某條記錄的時候能夠在頁目錄中使用二分法快速定位到對應的槽,而後再遍歷該槽對應分組中的記錄便可快速找到指定的記錄。若是不是主鍵的話,那麼只能遍歷單鏈表中的每條記錄對比查找。數據庫

因此,若是不用索引優化的話,那麼在進行一條查找的sql的話,默認的流程是這樣子的:服務器

  • 定位到記錄所在的頁(須要遍歷雙向鏈表,找到所在的頁)
  • 從所在的頁內中查找相應的記錄(是否是根據主鍵查詢,不是隻能遍歷所在頁的單鏈表了)

若是在數據量特別大的時候,又是極端狀況,遍歷雙向鏈表和單鏈表,速度就會顯得很是慢!數據結構

3 B-Tree索引與B+Tree索引

3.1 B-Tree索引結構

當人們開始談論索引的時候,若是沒有特別指明類型的話,那麼多半說的就是B-Tree(B樹)因此,它使用的是B-Tree數據結構來存儲數據,大多數的MySQL引擎都支持這種索引(但實際上不少存儲引擎使用的是B+Tree,這個咱們稍後再談到)。咱們這裏經過B-Tree索引結構就能夠極大的優化了上面的查找。性能

從圖中咱們能夠很明顯的感覺到,使用索引後,就不須要再遍歷雙向鏈表那樣去查找頁,而是經過目錄就能夠很快的定位到咱們的實際的頁。如查找id爲1的數據

  • 首先小於4,能夠肯定在p1下,指向磁盤頁2
  • 在磁盤頁裏面,小於2,指向p1,而後查找到

並且,咱們也能夠根據圖總結出B-Tree的特色:

  • 全部鍵值數據分佈在整棵樹各個節點中
  • 咱們的查找有可能在非節點結束,好比上圖中,咱們要找id爲4的數據的話,在根節點就能夠查找到
  • 全部葉子節點都在同一層,而且以升序排列

3.2 B+Tree索引結構

B+Tree索引是依據B-Tree索引基礎上的一次優化,具體變化以下:

  • B+Tree 非葉子節點不存放數據

  • 葉子節點存儲關鍵字和數據,非葉子節點的關鍵字也會沉到葉子節點,而且排序

  • 葉子節點兩兩指針相互鏈接,造成一個雙向環形鏈表(符合磁盤的預讀特性),順序查詢性能更高(區間查找更加方便)

咱們的B+Tree的優化到底有什麼好處呢?

首先是咱們的數據只放在了葉子節點上面。這個惟一的好處就是咱們的非葉子節點能夠存放更多的關鍵字了,總體就能夠存放更多的數據。由於咱們MySQL查詢過程是按頁加載數據的,每加載一頁就是一次IO操做,咱們根磁盤頁存放的數據越少,關鍵字越多,那麼總體的數據量就能夠說是越多。

還有一個好處就是,在葉子節點造成雙向環形鏈表。這樣子若是要進行區間查詢的話,只須要順着葉子節點的指針向下查詢就行。而若是是B-Tree的話,就須要返回上一級節點而後再讀取磁盤頁進行查找,節省了很多時間!

3.3 爲何不採用別的樹結構?

爲何要採用B-Tree的結構,甚至是B+Tree的結構呢?

其實仍是跟咱們的磁盤讀取的緣由有關。上面咱們說到了,MySQL查詢過程是按頁加載數據的。而咱們的操做系統通常將內存和磁盤分割成固定大小的塊,每一塊稱爲一頁,內存與磁盤以頁爲單位交換數據。咱們的內存每次讀取的就是MySQL分割成的一個頁大小,也就是一個索引點,圖中的一個磁盤頁。

採用普通二叉樹?不!

若是採用普通的二叉樹的話,咱們要考慮到一種狀況。那就是在極端的狀況下,一棵樹是會退化成鏈表的,那麼樹的優勢就沒有了。 這與咱們原來用雙向鏈表有何異同?

採用紅黑樹?不!

那麼可能有人說了,若是採用紅黑樹,樹保持平衡不就好了嗎?確實,紅黑樹等平衡樹也能夠用來實現索引,可是與咱們的B-Tree/B+Tree來講性能要差不少。

咱們上面說到了內存每一次I/O都是載入一個索引節點,也就是一個磁盤頁。若是數據不在同一個磁盤塊上,那麼一般須要移動制動手臂進行尋道,而制動手臂由於其物理結構致使了移動效率低下,從而增長磁盤數據讀取時間。B-Tree/B+Tree相對於紅黑樹有更低的樹高,進行尋道的次數與樹高成正比,在同一個磁盤塊上進行訪問只須要很短的磁盤旋轉時間,因此 B-Tree/B+Tree 樹更適合磁盤數據的讀取。

並且爲了減小磁盤 I/O 操做,磁盤每每不是嚴格按需讀取,而是每次都會預讀。預讀過程當中,磁盤進行順序讀取,順序讀取不須要進行磁盤尋道,而且只須要很短的磁盤旋轉時間,速度會很是快。而且能夠利用預讀特性,相鄰的節點也可以被預先載入。

4 哈希索引

MySQL除了B+樹以外,還有一種常見的是就是哈希索引。

哈希索引就是採用必定的哈希算法,把鍵值換算成新的哈希值,檢索時不須要相似B+樹那樣從根節點到葉子節點逐級查找,只需一次哈希算法便可馬上定位到相應的位置,速度很是快

本質上就是把鍵值換算成新的哈希值,根據這個哈希值來定位

使用哈希索引最大的好處就是速度特別快,咱們只須要一次定位就能夠找到咱們要的數據。時間複雜度爲O(1),可是咱們的InnoDB(MySQL默認存儲引擎)默認使用的倒是B+樹索引,這也是由於哈希索引有必定的缺點:

  • 沒法用於排序和分組
  • 只支持精確查找,沒法用於部分查找和範圍查找
  • 在有大量重複鍵值狀況下,哈希索引的效率也是極低的---->哈希碰撞問題。
  • 不支持最左匹配原則

但是若是一個索引值被頻繁使用的話,咱們的InnoDB會再B+Tree索引之上再建立一個哈希索引,用來方便快速查找。這個功能叫作「自適應哈希索引」。

5 聚簇索引與輔助索引

MySQL數據庫中innodb存儲引擎,B+樹索引能夠分爲聚簇索引(也稱彙集索引,clustered index)和輔助索引(有時也稱非聚簇索引或二級索引,secondary index,non-clustered index)。這兩種索引內部都是B+樹,彙集索引的葉子節點存放着一整行的數據。

Innodb中的主鍵索引是一種聚簇索引,非聚簇索引都是輔助索引,像複合索引、前綴索引、惟一索引。

5.1 聚簇索引

聚簇索引就是按照每張表的主鍵構造一顆B+樹,同時葉子節點中存放的就是整張表的行記錄數據,也將彙集索引的葉子節點稱爲數據頁。這個特性決定了索引組織表中數據也是索引的一部分,每張表只能擁有一個聚簇索引。

Innodb經過主鍵彙集數據,若是沒有定義主鍵,innodb會選擇非空的惟一索引代替。若是沒有這樣的索引,innodb會隱式的定義一個主鍵來做爲聚簇索引。

使用聚簇索引的優勢:

  • 數據訪問更快,由於聚簇索引將索引和數據保存在同一個B+樹中,所以從聚簇索引中獲取數據比非聚簇索引更快
  • 聚簇索引對於主鍵的排序查找和範圍查找速度很是快

缺點:

  • 插入速度嚴重依賴於插入順序,按照主鍵的順序插入是最快的方式,不然將會出現頁分裂,嚴重影響性能。所以,對於InnoDB表,咱們通常都會定義一個自增的ID列爲主鍵
  • 更新主鍵的代價很高,由於將會致使被更新的行移動。所以,對於InnoDB表,咱們通常定義主鍵爲不可更新。
  • 二級索引(輔助)訪問須要兩次索引查找,第一次找到主鍵值,第二次根據主鍵值找到行數據。

Innodb中聚簇索引示意圖:

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

5.2 輔助索引

聚簇索引之上建立的索引稱之爲輔助索引,輔助索引訪問數據老是須要二次查找。輔助索引葉子節點存儲的再也不是行的物理位置,而是主鍵值。經過輔助索引首先找到的是主鍵值,再經過主鍵值找到數據行的數據頁,再經過數據頁中的Page Directory(頁目錄)找到數據行。

Innodb輔助索引的葉子節點並不包含行記錄的所有數據,葉子節點除了包含鍵值外,還包含了相應行數據的聚簇索引鍵。輔助索引的存在不影響數據在聚簇索引中的組織,因此一張表能夠有多個輔助索引。在innodb中有時也稱輔助索引爲二級索引。

Innodb中輔助索引示意圖:

經過對比咱們就能夠知道爲何咱們對主鍵會有要求:

一、爲何不建議使用過長的字段做爲主鍵,由於全部輔助索引都引用主索引,過長的主索引會令輔助索引變得過大。

二、爲何用非單調的字段做爲主鍵在InnoDB中不是個好主意,由於InnoDB數據文件自己是一顆B+Tree,非單調的主鍵會形成在插入新記錄時數據文件爲了維持B+Tree的特性而頻繁的分裂調整,十分低效,而使用自增字段做爲主鍵則是一個很好的選擇。

固然,若是咱們經過索引優化,將輔助索引優化成覆蓋索引,那麼輔助索引也包含全部須要查詢的字段的值。也就是咱們的索引就是咱們要的值,無需再訪問主索引了。這裏,涉及到索引的優化不過多介紹!

5.3 MyISAM實現對比

上面我介紹了在InnoDB存儲引擎下的聚簇索引與輔助索引的實現,由於若是沒有說明具體的數據庫和存儲引擎,默認指的是MySQL中的InnoDB存儲引擎。可是咱們MySQL還支持MyISAM存儲引擎,它也是支持聚簇索引與輔助索引的。

可是該引擎下的實現卻有些不一樣,咱們的聚簇索引和輔助索引沒有什麼區別,他們的葉子節點都不存放數據,而是存放數據記錄的地址。惟一的區別就是聚簇索引要求key是惟一的,而輔助索引的key能夠重複

因此若是嚴格的按照聚簇索引葉子節點存放數據來定義的話,MyISAM的索引都只能算是非聚簇索引!聚簇索引,或者嚴格說主鍵索引示意圖:

輔助索引示意圖:

爲了更形象說明這兩種存儲引擎下兩種索引的區別,咱們假想一個表以下圖存儲了4行數據。其中Id做爲主索引,Name做爲輔助索引。圖示清晰的顯示了聚簇索引和非聚簇索引的差別。

5.4 索引優勢及使用

咱們經過結構對比了使用索引的好處,總結下來的話就是:

  • 大大減小了服務器須要掃描的數據行數
  • 幫助服務器避免進行排序和分組,以及避免建立臨時表(B+Tree 索引是有序的,能夠用於 ORDER BY 和 GROUP BY 操做。臨時表主要是在排序和分組過程當中建立,不須要排序和分組,也就不須要建立臨時表)
  • 將隨機 I/O 變爲順序 I/O(B+Tree 索引是有序的,會將相鄰的數據都存儲在一塊兒)

可是咱們要知道,索引並非最好的解決方案。總的來講,只有當索引幫助存儲引擎快速找到記錄帶來的好處大於其帶來的額外工做時,索引纔是有效的。

  • 對於很是小的表、大部分狀況下簡單的全表掃描比創建索引更高效;
  • 對於中到大型的表,索引就很是有效;
  • 可是對於特大型的表,創建和維護索引的代價將會隨之增加。這種狀況下,須要用到一種技術能夠直接區分出須要查詢的一組數據,而不是一條記錄一條記錄地匹配,例如可使用分區技術(具體能夠查看高性能MySQL第七章)。

6 總結

這裏僅僅是介紹了索引結構原理等,關於索引還有不少,如全文索引,空間索引等,以及索引的優化之類,這更可能是咱們要去學習的。

7 參考資料

高性能MySQL(第三版)

MySQL存儲結構

數據庫兩個神器索引和鎖(修訂版)

CS-Nodes聚簇索引與非聚簇索引

相關文章
相關標籤/搜索