MySQL 索引機制背後的隱藏之道

索引的 「哲學思想」

咱們爲何須要索引?php

顯而易見,使用索引能夠加快咱們檢索數據的速度,生活中書籍的目錄、圖書館裏的各類書架編號、號碼簿上的檢索頁等,都少不了索引的身影。html

回到計算機的世界,任何一種數據結構都不是憑空產生的,必定會有它的誕生背景和解決的問題。咱們先舉個最簡單的例子,下圖是一個有序遞增的數組,裏面包含十個元素,沒有重複。mysql

若是我想要查找元素 24 ,該怎麼作呢?第一想到的天然是遍歷數組,若是數組長度爲 N 那麼算法的時間複雜度是 O(N)。有沒有更快的辦法呢?隨即咱們想到,鑑於數組已經有序了,咱們還可使用二分查找,每次都折半,時間複雜度降爲 O(logN)。甚至於,咱們還能夠創建樹形的數據結構來搜索,最多見的就是二叉搜索樹(BST)或者 AVL 樹。程序員

到目前爲止,好像一切都很容易,下面咱們爲以前的數據再增長一個關聯的數據屬性(或者多個數據屬性)。算法

看看是否是有點眼熟,好像這種結構在哪裏見過?想象一下,將這個數據集橫向拓展,發現這其實就是數據庫中一張表,它有兩列,一列主鍵一列數字,其中第一行的數據就對應數據庫表的主鍵(Primary Key),每一個 PK 關聯與之對應的一整行數據記錄。sql

回想下咱們剛剛作的努力,咱們用 PK 的值來構建了某種查詢數據結構(例如 BSTAVL),而後經過它快速找到了 PK 的值,若是樹的節點保存一整行的記錄,那麼當咱們的查詢命中某個 PK 以後,就能在該節點順勢讀取到這一行其餘的數據了。數據庫

例如咱們查詢主鍵爲 27 的節點,即可以順勢讀到第二行的 7 這個數值。數組

上述的例子是很顯而易見的,即便你沒接觸過索引,要設計一種加速查詢的方法,也可能會想到這種方案,可是僅僅作到這些遠遠不夠,數據庫系統受龐大的數據量、查詢條件的複雜性(等值、範圍、模糊)的影響,其索引的實現複雜許多,可是起源的哲學思想都是同樣的服務器

索引是越多越好嗎?

雖然說索引能夠加速查詢,但索引未必是越多越好,由於:數據結構

  • 數據的增刪都會涉及到隨索引的修改,索引越多維護成本越高;
  • 索引越多也意味着存儲空間須要越大;
  • 有時候未必須要索引,若是一列數據重複項很是多,建索引反而沒有必要,例如第一節中咱們列舉了一個內存中、極少許數據如何採用不一樣的方法作高效查詢的例子,但其實這種容量的數組在內存中直接遍歷纔是王道,不須要關心性能問題。

數據庫對索引結構的要求

在真正的數據庫設計中,例如 MySQL 這樣的關係型數據庫,它對索引結構的設計也是有要求的:

  • 查詢速度要儘量快
  • 索引顯然也須要存儲在磁盤上進行持久化,如何儘量減小索引查詢過程當中的磁盤 IO 次數
  • 不只僅是等值查詢,也要能作範圍查詢(BETWEENIN<>)、模糊查詢(LIKE)、並集查詢(OR)

咱們來仔細思考下上面的三個要求,第一個要求顯然排除了線性數據結構,只能採用樹形結構,相比於 BST 在最差狀況下會退化至 O(N)AVL 樹由於加入了自平衡算法所以讀寫操做均能很好地保持 O(logN) 的時間複雜度。

咱們決定從平衡搜索樹中進行選擇,那爲何不選擇平衡二叉搜索樹呢,這得看第二項要求。索引在數據量大時是沒法所有讀進內存的,一般狀況下索引 : 數據量的比例能達到 1 : 5,若是一張表上多個列存在索引、聯合索引等,該比例還會繼續上升。磁盤上每存儲 1GB 的數據就要耗費 200MB 用來存儲索引,一旦數據量大內存是存放不下所有的索引的,何況索引不持久化難道每次啓動時都新建麼?所以索引必須在磁盤上存儲,讀入索引會產生磁盤 IO

衆所周知,相比內存(DRAM),磁盤讀取會慢上十萬倍,所以如何減小索引查找過程當中的磁盤 IO 次數相當重要,這個條件限制了二叉搜索樹成爲索引數據結構的機會,反而是高度可控的多路搜索樹更適合。所以,文件系統及數據庫系統廣泛採用 B+ 樹 做爲索引結構,至於爲何最終選擇 B+ 樹,它的優勢是什麼,弄清楚這些得先從磁盤 I/O 的知識入手,而後再結合這些原理分析 B+ 樹做爲索引結構的優點在哪裏。

磁盤 I/O 與磁盤預讀

本節內容選自《深刻理解計算機系統》第六章 存儲器層次結構

磁盤 I/O

先簡單介紹一下磁盤 I/O 和預讀,磁盤以扇區大小的塊來讀寫數據,對扇區的訪問時間主要有三個組成部分:尋道時間、旋轉時間和傳送時間。

尋道時間

爲了讀取某個扇區的內容,傳動臂須要首先將讀寫頭定位到包含目標扇區的磁道上,移動傳動臂所須要的時間稱爲尋道時間。尋道時間依賴於讀寫頭本來的位置和傳動臂在磁盤上的移動速度,主流磁盤通常在 3 ~ 9ms,最大尋道時間在 20ms

旋轉時間

一旦讀寫頭定位到了指望的磁道,驅動器等待目標扇區的第一個位旋轉到讀寫頭下,這個步驟的性能依賴於讀寫頭到達目標扇區的位置和磁盤的選擇速度。

傳送時間

當目標扇區的第一位位於讀寫頭下,驅動器就能夠開始讀或者寫該扇區的內容了。一個扇區的傳送速度依賴於旋轉速度和每條磁道的扇區數目。相對於前兩個時間,讀寫數據過程當中,傳送時間能夠忽略不計。

邏輯磁盤塊

現代磁盤構造複雜,有多個盤面,盤面又有不一樣的記錄區,爲了屏蔽複雜性,現代磁盤將它們組織成一種簡單的視圖,一個 B 個扇區大小的邏輯塊序列,編號 0,1 …… B-1。磁盤中有一個名爲磁盤控制器的固件設備,維護者邏輯塊號和實際物理磁盤扇區之間的映射關係。

當操做系統想要執行一個 I/O 操做時,例如讀取一個磁盤扇區的數據到主存,它就會發送一個命令到磁盤控制器,讓它讀取某個邏輯塊號,控制器上一個固件會將邏輯塊號翻譯爲由盤面磁道扇區三個元素組成的三元組,這個三元組惟一標識了一個物理扇區,而後驅動器將讀寫頭移動到指定位置,將數據讀到主存。

磁盤預讀

由於主存和磁盤訪問效率的巨大差別,磁盤 I/O 變成了一個很重量級的操做,所以須要儘量減小磁盤 I/O 的次數,爲了達到這個目的,磁盤每每不是嚴格按需讀取,而是每次都會預讀,即便只須要一個字節,磁盤也會從這個位置開始,順序向後讀取必定長度的數據放入內存。這樣作的理論依據是局部性原理,即當計算機訪問一個地址的數據的時候,一般與其相鄰的數據也會很快被訪問到。

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

想要詳細瞭解這部份內容,推薦閱讀《深刻理解計算機系統》第九章 虛擬存儲器

B+ 樹的優點

上文咱們介紹了磁盤讀取的一些知識,結合咱們以前說的 —— 索引結構的優劣與磁盤 I/O 次數大小緊密相關。所以合適的索引結構一定能最大限度發揮磁盤的性能,那 B+ 樹又是如何作到的呢?

B+ 樹的結構中可知,若是樹高是 h 的話,訪問一個葉子節點須要通過 h 次查詢操做,也即訪問 h 個節點。考慮索引實際上存儲在磁盤上,載入索引節點的過程須要經歷磁盤 I/OB+ 樹因爲出色的高度控制,致使 h 的值不會太大,通常來講百萬數量級能夠控制在 2 ~ 4 左右,意爲訪問節點的數量主須要 2 ~ 4 個。

數據庫系統的設計者又巧妙利用了磁盤預讀原理,將一個節點的大小設置成一個頁,這樣每一個節點只須要一次磁盤 I/O 就能夠載入主存。這樣的話,B+ 樹訪問一個葉子節點須要 h-1磁盤 I/O 就能夠,由於其根節點是常駐內存的,極大減小了磁盤 I/O 次數,提升了索引結構的效率

MySQL 中的索引

索引的類型有不少種,能夠爲不一樣的場景提供更好的性能。MySQL 中索引是在存儲引擎層面而不是服務器層面實現的,因此沒有統一的標準,不一樣存儲引擎的索引工做方式也不同。咱們先看看 MySQL 中支持的索引類型。

B-Tree 索引

雖然說叫 B-Tree 索引,甚至顯示時也是顯示成 BTREE,但其實內部實現多使用的是其變種 B+ 樹索引,大多數 MySQL 存儲引擎都支持這種索引,包括 InnoDB

所以,B+ 樹索引實際上就是咱們所說的傳統意義上的索引,也是目前關係型數據庫中最爲經常使用的、最有效的索引類型。B+ 樹在關係型數據庫的索引設計中如此流行主要得益於它的高扇出性B+ 樹索引的高度通常維持在 2 ~ 4 層,也就是說查詢某一鍵值的行記錄最多隻須要 2 ~ 4IO,極大減小了磁盤操做的次數。

哈希索引

基於哈希表實現,只有精確匹配全部列的查詢纔有效。實現方法爲,對於每一行數據,存儲引擎都會對全部的索引列計算出一個哈希碼,哈希碼是一個較小的值,哈希索引將全部行算出的哈希碼存儲在索引中,併爲每個哈希碼維護指向具體某一行的指針。

MySQL 中只有 Memory 引擎顯式支持哈希索引。InnoDB 支持的哈希索引是自適應的,用戶沒法進行配置,InnoDB 引擎會根據表的使用狀況自動爲表生成哈希索引。使用哈希索引的好處在於時間複雜度爲 O(1),所以哈希索引的查詢效率要遠高於 BTree 索引。可是其限制在於:

  1. 只有精確匹配索引全部列的查詢纔有效,由於哈希索引是利用索引的全部列的字段值來計算哈希值的。
  2. 只支持等值比較查詢,不能用於範圍查詢。
  3. 哈希索引的只包含索引字段的哈希值和指向數據的指針,因此不能使用索引中的值來避免讀取行。
  4. 哈希索引的數據並非順序存儲的,沒法用於排序。

空間數據索引

MyISAM 存儲引擎支持空間索引,能夠用做地理數據存儲。平日使用場景很少此處再也不詳述。

全文索引

全文索引是一種特殊的索引類型,它查找的是文本中的關鍵詞,而不是直接比較索引中的值。它更相似於搜索引擎作的事情,而不是簡單的 WHERE 條件匹配。實現方法是經過創建倒排索引,快速匹配文檔,這種實現方式也在 Apache Lucene 這種全文檢索庫中出現。

MyISAM 中的索引詳解

MyISAM 存儲引擎的索引文件和數據文件是分開的,MyISAM 引擎按照數據插入順序,將數據文件存儲在磁盤上,例以下圖中 99 條記錄從上到下依次存儲。MyISAM 引擎使用 B+ 樹做爲索引結構,葉節點存放的是數據記錄的行指針,圖中爲了方便閱讀以行號代替

MyISAM 引擎中,對主鍵列創建的主索引和對其餘列創建的輔助索引在結構上沒有區別,主鍵索引就是一個名爲 Primary 的惟一非空索引。

總結一下,MyISAM 引擎中索引查詢的步驟爲,先按照 B+ 樹查詢到葉子節點,若是指定的鍵值存在,則取出其對應的行指針的值,而後經過行指針,讀取相應數據行的記錄。

InnoDB 中的索引詳解

聚簇索引

MyISAM 引擎不一樣,InnoDB 的數據文件自己就是索引文件,表數據文件自己就是按 B+ 樹組織的一個索引結構,其葉子節點的鍵值就是表的主鍵,這種數據存儲方式也被稱爲聚簇索引。因而可知,聚簇索引並非一種單獨的索引類型,而是一種數據存儲方式。

聚簇索引的葉子節點都包含主鍵值、事務 ID、用於事務 MVCC 的回滾指針以及全部的剩餘列。

輔助索引

輔助索引也叫非聚簇索引,二級索引等。同 MyISAM 引擎的輔助索引實現不一樣,InnoDB 的輔助索引,其葉子節點存儲的不是行指針而是主鍵值,獲得主鍵值再要查詢具體行數據的話,要去聚簇索引中再查找一次,也叫回表。這樣的策略優點是減小了當出現行移動或者數據頁分裂時二級索引的維護工做。

參考資料

寫在最後

這是一個不定時更新的、披着程序員外衣的文青小號,歡迎關注。

相關文章
相關標籤/搜索