MySQL實戰 | 04 爲什麼要使用索引?

原文鏈接:MySQL實戰 | 爲什麼要使用索引?

用過 MySQL 的應該都知道索引是幹啥的吧,應該多少都設置過索引,但是若是問你索引是怎麼實現的,你能說上來嗎?

索引是什麼?

MySQL 官方對索引的定義爲:索引是幫助 MySQL 高效獲取數據的數據結構。

在數據之外,數據庫系統還維護着滿足特定查找算法的數據結構,這些數據結構以某種方式引用(指向)數據,這樣就可以在這些數據結構上實現高級查找算法。這種數據結構,就是索引。

索引的出現就是爲了提高查詢效率,就像書的目錄。其實說白了,索引要解決的就是查詢問題。

查詢,是數據庫所提供的一個重要功能,我們都想儘可能快的獲取到目標數據,因此就需要優化數據庫的查詢算法,選擇合適的查詢模型來實現索引。

另外,爲表設置索引要付出代價的:一是增加了數據庫的存儲空間,二是在插入和修改數據時要花費較多的時間,因爲索引也要隨之變動。

常見查詢模型

索引的實現模型有很多,這裏我們先了解一下常用的查詢模型。

順序數組

順序數組是一種特殊的數組,裏面的元素,按一定的順序排列。

順序數組在查詢上有着一定的優勢,因爲是有序的數據,採用二分查找的話,時間複雜度是 O(log(N))

二分查找

順序數組的優點就是查詢效率非常高,但是要更新數據的話,就非常麻煩了。刪除和插入元素都要涉及到大量元素位置的移動,成本很高。

因此,對於順序數組更適合用於查詢的領域,適合存儲一些改動較小的靜態存儲引擎。

哈希索引

哈希表是一種以 鍵-值(key-value) 存儲數據的結構,我們只要輸入待查找的值即 key,就可以找到其對應的值即 value。

哈希索引採用一定的哈希算法,對於每一行,存儲引擎計算出了被索引字段的哈希碼(Hash Code),把哈希碼保存在索引中,並且保存了一個指向哈希表中的每一行的指針。

這樣在檢索時只需一次哈希算法即可立刻定位到相應的位置,速度非常快。

Hash 索引結構的特殊性,其檢索效率非常之高,應該是 O(1) 的時間複雜度。

哈希索引

雖然 Hash 索引效率高,但是 Hash 索引本身由於其特殊性也帶來了很多限制和弊端,主要有以下這些:

1、Hash索引僅僅能滿足 =IN<=> 查詢,如果是範圍查詢檢索,這時候哈希索引就毫無用武之地了。

因爲原先是有序的鍵值,經過哈希算法後,有可能變成不連續的了,就沒辦法再利用索引完成範圍查詢檢索;

2、Hash 索引無法利用索引完成排序,因爲存放的時候是經過 Hash 計算過的,計算的 Hash 值和原始數據不一定相等,所以無法排序;

3、聯合索引中,Hash 索引不能利用部分索引鍵查詢。

Hash 索引在計算 Hash 值的時候是聯合索引鍵合併後再一起計算 Hash 值,而不是單獨計算 Hash 值。

所以對於聯合索引中的多個列,Hash 是要麼全部使用,要麼全部不使用。通過前面一個或幾個索引鍵進行查詢的時候,Hash 索引也無法被利用。

4、Hash索引在任何時候都不能避免表掃描。

前面已經知道,Hash 索引是將索引鍵通過 Hash 運算之後,將 Hash 運算結果的 Hash 值和所對應的行指針信息存放於一個 Hash 表中,由於不同索引鍵可能存在相同 Hash 值,所以即使取滿足某個 Hash 鍵值的數據的記錄條數,也無法從 Hash 索引中直接完成查詢,還是要通過訪問表中的實際數據進行相應的比較,並得到相應的結果。

5、在有大量重複鍵值情況下,哈希索引的效率也是極低的,因爲存在所謂的哈希碰撞問題。

綜上,哈希表這種結構適用於只有等值查詢的場景,比如 Memcached、redis 及其他一些 NoSQL 引擎。

二叉搜索樹索引

二叉搜索樹的每個節點都只存儲一個鍵值,並且左子樹(如果有)所有節點的值都要小於根節點的值,右子樹(如果有)所有節點的值都要大於根節點的值。

當二叉搜索樹的所有非葉子節點的左右子樹的節點數目均保持差不多時(平衡),這時樹的搜索性能逼近二分查找;並且它比連續內存空間的二分查找更有優勢的是,改變樹結構(插入與刪除結點)不需要移動大段的內存數據,甚至通常是常數開銷。

特殊情況下,根節點的左右子樹的高度相差不超過 1 時,這樣的二叉樹被稱爲平衡二叉樹;與之相對的是,二叉搜索樹有可能退化成線性樹。

平衡二叉樹|線性樹

下圖展示了一種可能的索引方式。左邊是數據表,一共有兩列七條記錄,最左邊的是數據記錄的物理地址(注意邏輯上相鄰的記錄在磁盤上也並不是一定物理相鄰的)。

二叉搜索樹

爲了加快 Col2 的查找,可以維護一個右邊所示的二叉查找樹,每個節點分別包含索引鍵值和一個指向對應數據記錄物理地址的指針,這樣就可以運用二叉查找在 O(log2n) 的複雜度內獲取到相應數據。

B樹

看得出來,二叉樹在查詢和修改上做到了一個平衡,都有着不錯的效率,但是現實是很少有數據庫引擎使用二叉樹來實現索引,爲什麼呢?

數據庫存儲大多不適用二叉樹,數據量較大時,樹高會過高。

你可以想象一下一棵 100 萬節點的平衡二叉樹,樹高 20,每個葉子結點就是一個塊,每個塊包含兩個數據,塊之間通過鏈式方式鏈接。

索引樹的存儲

樹高 20 的話,就要遍歷 20 個塊才能得到目標數據,索引存儲在磁盤時,這將是非常耗時的。

因此,爲了減少磁盤的讀取,查詢時就要儘量少的遍歷數據塊,因此一般使用 N 叉樹。


這裏就有了 B樹(Balanced Tree)。

B樹

究竟什麼是 B 樹?

我們先看一個例子:

一顆B樹

從上圖你能輕易的看到,一個內結點 x 若含有 n[x] 個關鍵字,那麼 x 將含有 n[x]+1 個子女。如含有 2 個關鍵字 D H 的內結點有 3 個子女,而含有 3 個關鍵字 Q T X 的內結點有 4 個子女。

B 樹的特性

普及一些概念:

節點的度:一個節點含有的子樹的個數稱爲該節點的度;
樹的度:一棵樹中,最大的節點的度稱爲樹的度;
葉節點或終端節點:度爲零的節點;
非終端節點或分支節點:度不爲零的節點;

首先定義兩個變量:d 爲大於 1 的一個正整數,稱爲 B 樹的度。h 爲一個正整數,稱爲 B 樹的高度。

B 樹是滿足下列條件的數據結構:

1、每個非葉子節點由 n-1 個 key 和 n 個指針組成,其中 d<=n<=2d。

2、每個葉子節點最少包含一個 key 和兩個指針,最多包含 2d-1 個 key 和 2d 個指針,葉節點的指針均爲 null 。

3、除根結點和葉子結點外,其它每個結點至少有 [ceil(m / 2)] 個孩子(其中 ceil(x) 是一個取上限的函數);

4、所有葉節點具有相同的深度,等於樹高 h,且葉子結點不包含任何關鍵字信息。

5、key 和指針互相間隔,節點兩端是指針。

6、一個節點中的 key 從左到右非遞減排列。

7、每個指針要麼爲 null,要麼指向另外一個節點。

8、每個非終端結點中包含有 n 個關鍵字信息: (n,P0,K1,P1,K2,P2,…,Kn,Pn)。

其中:
a) Ki (i=1…n) 爲關鍵字,且關鍵字按順序升序排序 K(i-1)< Ki。
b) Pi 爲指向子樹根的接點,且指針 P(i-1) 指向子樹種所有結點的關鍵字均小於 Ki,但都大於 K(i-1)。
c) 關鍵字的個數 n 必須滿足: [ceil(m / 2)-1]<= n <= m-1。

B 樹查找過程

由於 B 樹的特性,在 B 樹中按 key 檢索數據的算法非常直觀:首先從根節點進行二分查找,如果找到則返回對應節點的 data,否則對相應區間的指針指向的節點遞歸進行查找,直到找到節點或找到 null 指針,前者查找成功,後者查找失敗。

B 樹查找

如上圖所示,我們來模擬下查找文件 29 的過程:

1、根據根結點指針找到文件目錄的根磁盤塊 1,將其中的信息導入內存。【磁盤 IO 操作 1 次】

2、此時內存中有兩個文件名 17、35 和三個存儲其他磁盤頁面地址的數據。根據算法我們發現:17<29<35,因此我們找到指針 p2;

3、根據 p2 指針,我們定位到磁盤塊 3,並將其中的信息導入內存。【磁盤 IO 操作 20次】

4、此時內存中有兩個文件名 26,30 和三個存儲其他磁盤頁面地址的數據。根據算法我們發現:26<29<30,因此我們找到指針 p2;

5、根據 p2 指針,我們定位到磁盤塊 8,並將其中的信息導入內存。【磁盤 IO 操作 3 次】;

6、此時內存中有兩個文件名 28,29。根據算法我們查找到文件名 29,並定位了該文件內存的磁盤地址。

分析上面的過程,發現需要 3 次磁盤 IO 操作和 3 次內存查找操作。關於內存中的文件名查找,由於是一個有序表結構,可以利用折半查找提高效率。

B+ 樹

B+ 樹:是應文件系統所需而產生的一種 B 樹的變形樹。

一棵 m 階的 B+ 樹和 m 階的 B 樹的異同點在於:

1、每個節點的指針上限爲 2d 而不是2d+1。

2、所有的葉子結點中包含了全部關鍵字的信息,及指向含有這些關鍵字記錄的指針,且葉子結點本身依關鍵字的大小自小而大的順序鏈接。(B 樹的葉子節點並沒有包括全部需要查找的信息)

3、所有的非終端結點可以看成是索引部分,結點中僅含有其子樹根結點中最大(或最小)關鍵字,不存儲 data。(B 樹的非終節點也包含需要查找的有效信息)

一顆 B+ 樹

爲什麼說 B+ 樹比 B 樹更適合做數據庫索引?

1)B+ 樹的磁盤讀寫代價更低

B+ 樹的內部結點並沒有存儲關鍵字具體信息。因此其內部結點相對 B 樹更小。

如果把所有同一內部結點的關鍵字存放在同一盤塊中,那麼盤塊所能容納的關鍵字數量也越多。一次性讀入內存中的需要查找的關鍵字也就越多。相對來說 IO 讀寫次數也就降低了。

  1. B+ 樹的查詢效率更加穩定

由於非終端結點並不是最終指向文件內容的結點,而只是葉子結點中關鍵字的索引。所以任何關鍵字的查找必須走一條從根結點到葉子結點的路。所有關鍵字查詢的路徑長度相同,進而每一個數據的查詢效率相當。

幾種樹的對比

二叉樹-B 樹

B+ 樹

以上,爲了介紹索引內容,我們花費了大量的篇幅介紹了幾種數據結構模型,特別是樹的相關概念。

另外,涉及到樹的添加和刪除元素,操作更加複雜,本文篇幅有限(其實是小編也搞不太明白),這裏就不再展開。

有興趣的,強烈建議鑽研下參考鏈接裏的內容。

好了,下面我們來看 MySQL 中的 InnoDB 引擎的索引是如何實現的。

MySQL 的索引模型

說了這麼多,終於到索引出場了。

索引就是這種神奇偉大的存在。索引相當於數據庫的表數據之外新建的數據結構,該數據結構的數據段中存儲着字段的值以及指向實際數據記錄的指針。

數據庫表的索引從數據存儲方式上可以分爲聚簇索引和非聚簇索引(又叫二級索引)兩種。

1、聚簇索引

表數據按照索引的順序來存儲的,也就是說索引項的順序與表中記錄的物理順序一致。

對於聚簇索引,葉子結點即存儲了真實的數據行,不再有另外單獨的數據頁。 在一張表上最多隻能創建一個聚集索引,因爲真實數據的物理順序只能有一種。

聚簇集是指實際的數據行和相關的鍵值都保存在一起。

注意:數據的物理存放順序與索引順序是一致的,即:只要索引是相鄰的,那麼對應的數據一定也是相鄰地存放在磁盤上的。

如果主鍵不是自增 id,那麼可以想象,它會幹些什麼,不斷地調整數據的物理地址、分頁,當然也有其他一些措施來減少這些操作,但卻無法徹底避免。但,如果是自增的,那就簡單了,它只需要一頁一頁地寫,索引結構相對緊湊,磁盤碎片少,效率也高。

聚簇索引的二級索引:葉子節點不會保存引用的行的物理位置,而是保存了行的主鍵值

2、非聚集索引

表數據存儲順序與索引順序無關。對於非聚集索引,葉結點包含索引字段值及指向數據頁數據行的邏輯指針,其行數量與數據錶行數據量一致。

聚簇索引是對磁盤上實際數據重新組織以按指定的一個或多個列的值排序的算法。特點是存儲數據的順序和索引順序一致。一般情況下主鍵會默認創建聚簇索引,且一張表只允許存在一個聚簇索引。

這兩個名字雖然都叫做索引,但這並不是一種單獨的索引類型,而是一種數據存儲方式。

下面,我們可以看一下 MYSQL 中 MyISAM 和 InnoDB 兩種引擎的索引結構。

MyISAM索引實現

MyISAM 引擎使用 B+ 樹作爲索引結構,葉節點的 data 域存放的是數據記錄的地址,就是非聚集索引。

下圖是 MyISAM 索引的原理圖:

MyISAM索引實現

在 MyISAM 中,主鍵索引和輔助索引(Secondary key)在結構上沒有任何區別,只是主鍵索引要求 key 是唯一的,而輔助索引的 key 可以重複。

InnoDB索引實現

雖然 InnoDB 也使用 B+ 樹作爲索引結構,但具體實現方式卻與 MyISAM 截然不同。

第一個重大區別是 InnoDB 的數據文件本身就是索引文件

在 InnoDB 中,表數據文件本身就是按 B+ 樹組織的一個索引結構,這棵樹的葉節點 data 域保存了完整的數據記錄。這個索引的 key 是數據表的主鍵,因此 InnoDB 表數據文件本身就是主索引。

另外,第二個與 MyISAM 索引的不同是 InnoDB 的輔助索引 data 域存儲相應記錄主鍵的值而不是地址

對於聚簇索引存儲來說,行數據和主鍵 B+ 樹存儲在一起,輔助索引只存儲輔助鍵和主鍵,主鍵和非主鍵 B+ 樹幾乎是兩種類型的樹。

對於非聚簇索引存儲來說,主鍵 B+ 樹在葉子節點存儲指向真正數據行的指針,而非主鍵。


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

元數據

檢索過程的差異

對於聚簇索引,若使用主鍵索引進行查詢where id = 14 這樣的條件查找主鍵,則按照 B+ 樹的檢索算法即可查找到對應的葉節點,之後獲得行數據。

若使用輔助索引進行查詢,對 Name 列進行條件搜索,則需要兩個步驟:

1、第一步在輔助索引 B+ 樹中檢索 Name,到達其葉子節點獲取對應的主鍵
2、第二步根據主鍵在主索引 B+ 樹種再執行一次 B+ 樹檢索操作,最終到達葉子節點即可獲取整行數據。這個過程稱爲回表

聚簇索引的優勢在哪?

1、由於行數據和葉子節點存儲在一起,這樣主鍵和行數據是一起被載入內存的,找到葉子節點就可以立刻將行數據返回了,如果按照主鍵 Id 來組織數據,獲得數據更快。

2、輔助索引使用主鍵作爲指針而不是使用地址值作爲指針的好處是,減少了當出現行移動或者數據頁分裂時輔助索引的維護工作。

使用主鍵值當作指針會讓輔助索引佔用更多的空間,換來的好處是 InnoDB 在移動行時無須更新輔助索引中的這個指針

也就是說行的位置會隨着數據庫裏數據的修改而發生變化,使用聚簇索引就可以保證不管這個主鍵 B+ 樹的節點如何變化,輔助索引樹都不受影響。

小結

這次內容比較多,涉及到了一些數據結構的內容,我也是翻了很多博客才搞懂那麼一點點。主要是要搞懂,爲什麼要用索引,以及索引的查詢流程。

希望對你有用。


參考:
http://blog.codinglabs.org/articles/theory-of-mysql-index.html
https://blog.csdn.net/v_JULY_v/article/details/6530142


你的關注是對我最大的鼓勵!

最近蒐集到傳智播客 2018 最新 Python 和 Java 教程!關注本公衆號,後臺回覆「2018」即可獲取下載地址。

公衆號提供CSDN資源免費下載服務!


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