MySQL底層索引深度解刨

做者:王新華html

爲何須要索引?
一句話歸納:索引的出現其實就是爲了提升數據查詢的效率。mysql

1、索引常見模型

模型: 哈希表、有序數組和搜索樹redis

哈希表

  • 哈希表是一種以鍵 - 值(key-value)存儲數據的結構,咱們只要輸入待查找的鍵即 key,就能夠找到其對應的值即 Value。哈希的思路很簡單,把值放在數組裏,用一個哈希函數把 key 換算成一個肯定的位置,而後把 value 放在數組的這個位置。
    時間複雜度:0(1)
  • 畫重點:若是索引的值有重複的話,會發生hash碰撞,雖然能夠解決hash衝突,可是致使查詢效率下降。
  • 場景: 哈希表這種結構適用於只有等值查詢的場景,不適用於查找範圍的。相似 redis Memcached 及其餘一些 NoSQL 引擎。

搜索樹

在瞭解搜索樹以前咱們須要先知道 二叉樹、平衡二叉樹、B樹、B+樹算法

推薦一個工具能夠清晰的理解樹的原理: https://www.cs.usfca.edu/~gal...sql

二叉樹

二叉樹特性: 左子樹的鍵值小於根的鍵值,右子樹的鍵值大於根的鍵值。數據庫

以下圖就是一個二叉樹
image.png數組

當前二叉樹的插入順序是: 3 2 4 1 5數據結構

若是咱們按1 2 3 4 5 的順序來插入的化, 咱們獲得的二叉樹就是以下圖所示
image.png
若是是這種二叉樹查詢效率就過低了。若想二叉樹的查詢效率儘量高,須要二叉樹是平衡的從而引出新的定義 - 平衡二叉樹或稱AVL樹。函數

平衡二叉樹

平衡二叉樹特色:工具

  • 知足二叉樹的特性
  • 任何節點的兩個子樹的高度最大差爲1

如上兩個組合以後就是平衡二叉樹,以下圖
image.png

中插入樹的順序爲:1 2 3 4 5 6 7 8 時候生成的是如圖所示的內容,和圖二完成不同, 在經過工具生成這個圖的時候明顯能夠看到無論如何插入節點數據都能知足平衡二叉樹的特色2。

當刪除一個節點以後一樣能知足avl樹,以下圖刪除圖3中的5節點以後展現以下。

image.png

總結一下 平衡二叉樹的優勢

  • a 不錯的查找性能(O(logn)), 不存在極端的低效查找的狀況。
  • b 能夠實現範圍查找、數據排序。

看起來 AVL 樹做爲數據查找的數據結構確實很不錯,可是 AVL 樹並不適合作 Mysql 數據庫的索引數據結構,由於考慮一下這個問題:

數據庫查詢數據的瓶頸在於磁盤 IO,若是使用的是 AVL 樹,咱們每個樹節點只存儲了一個數據,咱們一次磁盤 IO 只能取出來一個節點上的數據加載到內存裏,那好比查詢 id=8 這個數據咱們就要進行磁盤 IO 三次,這是多麼消耗時間的。因此咱們設計數據庫索引時須要首先考慮怎麼儘量減小磁盤 IO 的次數。

磁盤 IO 有個有個特色,就是從磁盤讀取 1B 數據和 1KB 數據所消耗的時間是基本同樣的,咱們就能夠根據這個思路,咱們能夠在一個樹節點上儘量多地存儲數據,一次磁盤 IO 就多加載點數據到內存,這就是 B 樹,B+樹的的設計原理了。

B-樹

B樹的特色:

  • 全部健值分佈在整棵樹中
  • 搜索有可能在非葉子節點結束
  • 根節點至少有2個子樹
  • 全部葉子節點都在同一層
  • 分叉數(路數)永遠比關鍵字數多 1
  • 節點存儲key和value

image.png

演示 B樹索引分裂合併

好比 Max Degree(路數)是 3 的時候,咱們插入數據 一、二、3,在插入 3 的時候, 原本應該在第一個磁盤塊,可是若是一個節點有三個關鍵字的時候,意味着有 4 個指針, 子節點會變成 4 路,因此這個時候必須進行分裂。把中間的數據 2 提上去,把 1 和 3 變 成 2 的子節點。

若是刪除節點,會有相反的合併的操做。 注意這裏是分裂和合並,跟 AVL 樹的左旋和右旋是不同的。 咱們繼續插入 4 和 5,B Tree 又會出現分裂和合並的操做。
image.png
從這個裏面咱們也能看到,在更新索引的時候會有大量的索引的結構的調整,節點的分裂和合並,其實就是 InnoDB 頁的分裂和合並。

B+樹

B+樹是在B樹上作的一個優化工做

  • B+樹每一個節點能夠包含更多的節點內容,爲何這麼作呢?第一:下降樹的高度 第二:將數據範圍變爲多個區間,區間越多 檢索速度就越快
  • 非葉子節點存的是key, 葉子節點存的是key和數據
  • 葉子節點指針相連,順序查找速度更快
    image.png

B 樹和 B+樹有什麼不一樣呢?

第一,B 樹一個節點裏存的是key和數據,而 B+樹存儲的是索引(地址),因此 B 樹裏一個節點存不了不少個數據,可是 B+樹一個節點能存不少索引,B+樹葉子節點存全部的數據。

第二,B+樹的葉子節點是數據階段用了一個鏈表串聯起來,便於範圍查找。

經過 B 樹和 B+樹的對比咱們看出,B+樹節點存儲的是索引,在單個節點存儲容量有限的狀況下,單節點也能存儲大量索引,使得整個 B+樹高度下降,減小了磁盤 IO。其次,B+樹的葉子節點是真正數據存儲的地方,葉子節點用了鏈表鏈接起來,這個鏈表自己就是有序的,在數據範圍查找時,更具有效率。所以 Mysql 的索引用的就是 B+樹,B+樹在查找效率、範圍查找中都有着很是不錯的性能。

綜上所述mysql innodb 索引選擇了B+樹。

思考問題

一、B+樹葉子節點存的詩全部數據,若是1000萬數據,那這個鏈表太大 ,不會影響性能嗎?

能夠利用數據頁,下面有重點分析

二、InnoDB一棵B+樹能夠存放多少行數據?

B+Tree 的根節點和枝節點中都不會存儲數據,只有葉子節點才存儲數據。搜索 到關鍵字不會直接返回,會到最後一層的葉子節點。好比咱們搜索 id=3,雖然在第一 層直接命中了,可是所有的數據在葉子節點上面,因此我還要繼續往下搜索,一直到葉 子節點。
舉個例子:假設一條記錄是 1K,一個葉子節點(一頁)能夠存儲 16 條記錄。非葉 子節點能夠存儲多少個指針?
假設索引字段是 bigint 類型,長度爲 8 字節。指針大小在 InnoDB 源碼中設置爲 6 字節,這樣一共 14 字節。非葉子節點(一頁)能夠存儲 1024*16 / 14 = 1170 個這樣的 單元(鍵值+指針),表明有 1170 個指針。
樹深度爲 2 的時候, 有 1170^2 個葉子節點 ,能夠存儲的數據爲:
1170 * 1170 * 16 = 21902400 2000萬
在查找數據時一次頁的查找表明一次 IO,也就是說,一張 2000 萬左右的表,查詢數據最多須要訪問 3 次磁盤。 因此在 InnoDB 中 B+ 樹深度通常爲 1-3 層,它就能知足千萬級的數據存儲。

image.png

2、innodb的索引分析

在 InnoDB 中,表都是根據主鍵順序以索引的形式存放的,這種存儲方式的表稱爲索引組織表。

上述中咱們從不一樣維度分析了最終InnoDB 使用了 B+ 樹索引模型,因此數據都是存儲在 B+ 樹中的。

每個索引在 InnoDB 裏面對應一棵 B+ 樹。假設,咱們有一個主鍵列爲 ID 的表,表中有字段 k,而且在 k 上有普通索引。

這個表的建表語句是:

create table user(
id int primary key,
k int not null,
name varchar(16),
index (k))engine=InnoDB;

表中 第一行到第五行 的 (ID,k) 值分別爲 (100,1)、(200,2)、(300,3)、(500,5) 和 (600,6),兩棵樹的示例示意圖以下。

image.png

上圖爲:主健索引

image.png

上圖爲:普通索引

根據上面的索引結構說明,咱們來討論一個問題:基於主鍵索引和普通索引的查詢有什麼區別?

若是語句是 select * from T where ID=500,即主鍵查詢方式,則只須要搜索 ID 這棵 B+ 樹;

若是語句是 select * from T where k=5,即普通索引查詢方式,則須要先搜索 k 索引樹,獲得 ID 的值爲 500,再到 ID 索引樹搜索一次。這個過程稱爲回表。也就是說,基於非主鍵索引的查詢須要多掃描一棵索引樹。所以,咱們在應用中應該儘可能使用主鍵查詢。

B+ 樹爲了維護索引有序性,在插入新值的時候須要作必要的維護。以上面這個圖爲例,若是插入新的行 ID 值爲 700,則只須要在 R5 的記錄後面插入一個新記錄。如圖

image.png

若是新插入的 ID 值爲 400,就相對麻煩了,須要邏輯上挪動後面的數據,空出位置,如圖。

image.png

而更糟的狀況是,若是 R5 所在的數據頁已經滿了,根據 B+ 樹的算法,這時候須要申請一個新的數據頁,而後挪動部分數據過去。這個過程稱爲頁分裂。在這種狀況下,性能天然會受影響。

除了性能外,頁分裂操做還影響數據頁的利用率。本來放在一個頁的數據,如今分到兩個頁中,總體空間利用率下降大約 50%。固然有分裂就有合併。

當相鄰兩個頁因爲刪除了數據,利用率很低以後,會將數據頁作合併。合併的過程,能夠認爲是分裂過程的逆過程。

3、innodb 數據頁

在上述中咱們提到數據頁,數據頁的概念,它是MySQL管理存儲空間的基本單位,一個頁的大小通常是16KB,而且咱們知道了記錄實際上是被存放在頁中的,若是記錄佔用的空間太大還可能形成行溢出現象,這會致使一條記錄被分散存儲在多個頁中。

頁的本質就是一塊16KB大小的存儲空間,InnoDB爲了避免同的目的而把頁分爲不一樣的類型,其中用於存放記錄的頁也稱爲數據頁,咱們先看看這個用於存放記錄的頁長什麼樣。數據頁表明的這塊16KB大小的存儲空間能夠被劃分爲多個部分,不一樣部分有不一樣的功能,各個部分如圖所示:

image.png

從圖中能夠看出,一個InnoDB數據頁的存儲空間被劃分紅了7個部分,每一個部分又能夠被劃分爲若干小部分。

下面用數據頁來分析innodb索引數據.

image.png

咱們已主鍵索引爲例子,每一頁默認大小16k, 當咱們第一頁用戶數據區域頁滿了的時候 就會進行申請新的頁也就是第二頁 而後有新的數據插入時候會在第二頁中存入咱們的用戶數據好比如圖中的R4,涉及到這種數據的檢索頁內的數據是相似一個鏈表,頁與頁之間也是鏈表,當數據量大的時候其實性能比較差的,這個時候咱們須要引入頁目錄的概念。

image.png

當有了頁目錄以後檢索性能會提高不少,可是若是頁不少的時候其實性能也會存在弊端,那這個時候須要如何處理呢,這個時候咱們須要在上層在加入頁目錄的一個鏈表。

相關文章
相關標籤/搜索