MySQL相關(三)- 索引數據模型推演及 B+Tree 的詳細介紹

前言

前面已經寫了有兩篇章長度的文章,第三篇我一直在尋思着要寫什麼(其實並無),按照腦圖來的話,這篇文章咱們該來說講關於索引的知識了,這但是 MySQL 性能優化很關鍵的知識點,千萬千萬不要錯過,不過我這裏會相對比較深刻地探究,相信你們讀完以後多少會有點收穫。php

先送上兩張飛機票🛬還沒讀過前面文章的夥伴能夠先前往閱讀,由淺入深: MySQL相關(一)- 一條查詢語句是如何執行的 MySQL相關(二)- 一條更新語句是如何執行的html

因爲索引的知識點比較多,官網的內容也不少,若是你們想詳細瞭解能夠到官網,想先通讀了解的話能夠先看看我對索引的總結,這一章節分爲三部分來說:mysql

  1. innodb 邏輯存儲結構須要瞭解,做爲番外篇 MySQL相關(番外篇)- innodb 邏輯存儲結構
  2. 索引的數據結構也做爲另外的篇章,經過對查詢算法的數據模型進行演算分析 MySQL相關(三)- 索引數據模型推演及 B+Tree 的詳細介紹
  3. 對索引的使用及優化規則也會做爲單獨的篇章 MySQL相關(四)- 性能優化關鍵點索引

前面提到的腦圖以下,想要完整高清圖片能夠到微信個人公衆號下【6曦軒】下回復 MySQL 腦圖獲取: 在這裏插入圖片描述程序員

正文

MySQL索引數據模型推演

二分查找

雙十一過去以後,你女友跟你玩了一個猜數字的遊戲。(假設程序員 new 了一個會購物的女友出來) 猜猜我昨天買了多少錢,給你五次機會。 10000?低了。30000?高了。 接下來你會猜多少? 20000。爲何你不猜 11000,也不猜 29000 呢?面試

其實這個就是二分查找的一種思想,也叫折半查找,每一次,咱們都把候選數據縮小了一半。若是數據已經排過序的話,這種方式效率比較高。算法

因此第一個,咱們能夠考慮用有序數組做爲索引的數據結構。sql

有序數組的等值查詢和比較查詢效率很是高,可是更新數據的時候會出現一個問題,可能要挪動大量的數據(改變 index),因此只適合存儲靜態的數據。數據庫

爲了支持頻繁的修改,好比插入數據,咱們須要採用鏈表。鏈表的話,若是是單鏈表,它的查找效率仍是不夠高。數組

那麼,有沒有可使用二分查找的鏈表呢?性能優化

爲了解決這個問題,BST(Binary Search Tree)也就是咱們所說的二叉查找樹誕生了。

二叉查找樹

二叉查找樹的特色是什麼? 左子樹全部的節點都小於父節點,右子樹全部的節點都大於父節點。投影到平面之後,就是一個有序的線性表。

在這裏插入圖片描述

二叉查找樹既可以實現快速查找,又可以實現快速插入。

  • 可是二叉查找樹有一個問題:

它的查找耗時是和這棵樹的深度相關的,在最壞的狀況下時間複雜度會退化成 O(n)。

  • 什麼狀況是最壞的狀況呢?

咱們打開這樣一個網站來看一下,這裏面有各類各樣的數據結構的動態演示,包括 BST 二叉查找樹: https://www.cs.usfca.edu/~galles/visualization/Algorithms.html 仍是剛纔的這一批數字,若是咱們插入的數據恰好是有序的,二、六、十一、1三、1七、 22。 這個時候咱們的二叉查找樹變成了什麼樣了呢? 在這裏插入圖片描述 它會變成鏈表(這種樹也叫作「斜樹」),這種狀況下不能達到加快檢索速度的目的,和順序查找效率是沒有區別的。

  • 形成它傾斜的緣由是什麼呢?

由於左右子樹深度差太大,這棵樹的左子樹根本沒有節點——也就是它不夠平衡。

  • 因此,咱們有沒有左右子樹深度相差不是那麼大,更加平衡的樹呢?

這個就是平衡二叉樹,叫作 Balanced binary search trees,或者 AVL 樹(AVL 是發明這個數據結構的人的名字)。

平衡二叉樹

AVL Trees (Balanced binary search trees) 平衡二叉樹的定義:

左右子樹深度差絕對值不能超過 1。

  • 是什麼意思呢?

好比左子樹的深度是 2,右子樹的深度只能是 1 或者 3。 這個時候咱們再按順序插入 一、二、三、四、五、6,必定是這樣,不會變成一棵「斜樹」。

  • 那它的平衡是怎麼作到的呢?怎麼保證左右子樹的深度差不能超過 1 呢?

https://www.cs.usfca.edu/~galles/visualization/AVLtree.html 插入 一、二、3。 咱們注意看:當咱們插入了 一、2 以後,若是按照二叉查找樹的定義,3 確定是要在 2 的右邊的,這個時候根節點 1 的右節點深度會變成 2,可是左節點的深度是 0,由於它沒有子節點,因此就會違反平衡二叉樹的定義。 右-右型左旋平衡 那應該怎麼辦呢?由於它是右節點下面接一個右節點,右-右型,因此這個時候咱們要把 2 提上去,這個操做叫作左旋。 左-左型右旋 一樣的,若是咱們插入 七、六、5,這個時候會變成左左型,就會發生右旋操做,把 6 提上去。 因此爲了保持平衡,AVL 樹在插入和更新數據的時候執行了一系列的計算和調整的操做。

  • 平衡的問題咱們解決了,那麼平衡二叉樹做爲索引怎麼查詢數據?

  • 在平衡二叉樹中,一個節點,它的大小是一個固定的單位,做爲索引應該存儲什麼內容?

它應該存儲三塊的內容:

  1. 索引的鍵值。好比咱們在 id 上面建立了一個索引,我在用 where id =1 的條件查詢的時候就會找到索引裏面的 id 的這個鍵值。

  2. 數據的磁盤地址,由於索引的做用就是去查找數據的存放的地址。

  3. 由於是二叉樹,它必須還要有左子節點和右子節點的引用,這樣咱們才能找到下一個節點。好比大於 26 的時候,走右邊,到下一個樹的節點,繼續判斷。

若是是這樣存儲數據的話,咱們來看一下會有什麼問題。 在分析用 AVL 樹存儲索引數據以前,咱們先來學習一下 InnoDB 的邏輯存儲結構。 innodb 的邏輯存儲結構

AVL 樹用於存儲索引數據

首先,索引的數據,是放在硬盤上的。查看數據和索引的大小:

SELECT
	CONCAT(
		ROUND(SUM(DATA_LENGTH / 1024 / 1024) , 2) ,
		'MB'
	) AS data_len ,
	CONCAT(
		ROUND(SUM(INDEX_LENGTH / 1024 / 1024) , 2) ,
		'MB'
	) AS index_len
FROM
	information_schema. TABLES
WHERE
	table_schema = 'gupao'
AND table_name = 'user_innodb';

當咱們用樹的結構來存儲索引的時候,訪問一個節點就要跟磁盤之間發生一次 IO。

InnoDB 操做磁盤的最小的單位是一頁(或者叫一個磁盤塊),大小是 16K(16384 字節)。

那麼,一個樹的節點就是 16K 的大小。

若是咱們一個節點只存一個鍵值+數據+引用,例如整形的字段,可能只用了十幾個或者幾十個字節,它遠遠達不到 16K 的容量,因此訪問一個樹節點,進行一次 IO 的時候,浪費了大量的空間。

因此若是每一個節點存儲的數據太少,從索引中找到咱們須要的數據,就要訪問更多的節點,意味着跟磁盤交互次數就會過多。

若是是機械硬盤時代,每次從磁盤讀取數據須要 10ms 左右的尋址時間,交互次數越多,消耗的時間就越多。

在這裏插入圖片描述 好比上面這張圖,咱們一張表裏面有 6 條數據,當咱們查詢 id=37 的時候,要查詢兩個子節點,就須要跟磁盤交互 3 次,若是咱們有幾百萬的數據呢?這個時間更加難以估計。

  • 因此咱們的解決方案是什麼呢?
  1. 讓每一個節點存儲更多的數據。
  2. 節點上的關鍵字的數量越多,咱們的指針數也越多,也就是意味着能夠有更多的分叉(咱們把它叫作「路數」)。

由於分叉數越多,樹的深度就會減小(根節點是 0)。

這樣,咱們的樹是否是從原來的高瘦高瘦的樣子,變成了矮胖矮胖的樣子?

這個時候,咱們的樹就再也不是二叉了,而是多叉,或者叫作多路。

多路平衡查找樹(B Tree)(分裂、合併)

Balanced Tree 這個就是咱們的多路平衡查找樹,叫作 B Tree(B 表明平衡)。 跟 AVL 樹同樣,B 樹在枝節點和葉子節點存儲鍵值、數據地址、節點引用。 它有一個特色:分叉數(路數)永遠比關鍵字數多 1。好比咱們畫的這棵樹,每一個節 點存儲兩個關鍵字,那麼就會有三個指針指向三個子節點。

在這裏插入圖片描述 B Tree 的查找規則是什麼樣的呢? 好比咱們要在這張表裏面查找 15。 由於 15 小於 17,走左邊。 由於 15 大於 12,走右邊。 在磁盤塊 7 裏面就找到了 15,只用了 3 次 IO。 這個是否是比 AVL 樹效率更高呢? 那 B Tree 又是怎麼實現一個節點存儲多個關鍵字,還保持平衡的呢?跟 AVL 樹有什 麼區別?

https://www.cs.usfca.edu/~galles/visualization/Algorithms.html

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

若是刪除節點,會有相反的合併的操做。 注意這裏是分裂和合並,跟 AVL 樹的左旋和右旋是不同的。 咱們繼續插入 4 和 5,B Tree 又會出現分裂和合並的操做。

在這裏插入圖片描述 從這個裏面咱們也能看到,在更新索引的時候會有大量的索引的結構的調整,因此解釋了爲何咱們不要在頻繁更新的列上建索引,或者爲何不要更新主鍵。 節點的分裂和合並,其實就是 InnoDB 頁的分裂和合並。

B+樹(增強版多路平衡查找樹)

B Tree 的效率已經很高了,爲何 MySQL 還要對 B Tree 進行改良,最終使用了 B+Tree 呢?整體上來講,這個 B 樹的改良版本解決的問題比 B Tree 更全面。 咱們來看一下 InnoDB 裏面的 B+樹的存儲結構:

在這裏插入圖片描述

MySQL 中的 B+Tree 有幾個特色:

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

咱們來看一下 B+Tree 的數據搜尋過程:

1)好比咱們要查找 28,在根節點就找到了鍵值,可是由於它不是頁子節點,因此會繼續往下搜尋,28 是[28,66)的左閉右開的區間的臨界值,因此會走中間的子節點,而後繼續搜索,它又是[28,34)的左閉右開的區間的臨界值,因此會走左邊的子節點,最後在葉子節點上找到了須要的數據。

2)第二個,若是是範圍查詢,好比要查詢從 22 到 60 的數據,當找到 22 以後,只須要順着節點和指針順序遍歷就能夠一次性訪問到全部的數據節點,這樣就極大地提升了區間查詢效率(不須要返回上層父節點重複遍歷查找)。

總結一下,InnoDB 中的 B+Tree 的特色:

  1. 它是 B Tree 的變種,B Tree 能解決的問題,它都能解決。B Tree 解決的兩大問題是什麼?(每一個節點存儲更多關鍵字;路數更多)
  2. 掃庫、掃表能力更強(若是咱們要對錶進行全表掃描,只須要遍歷葉子節點就能夠了,不須要遍歷整棵 B+Tree 拿到全部的數據)
  3. B+Tree 的磁盤讀寫能力相對於 B Tree 來講更強(根節點和枝節點不保存數據區,因此一個節點能夠保存更多的關鍵字,一次磁盤加載的關鍵字更多)
  4. 排序能力更強(由於葉子節點上有下一個數據區的指針,數據造成了鏈表)
  5. 效率更加穩定(B+Tree 永遠是在葉子節點拿到數據,因此 IO 次數是穩定的)

爲何不用紅黑樹?

紅黑樹也是 BST 樹,可是不是嚴格平衡的。

必須知足 5 個約束:

  1. 節點分爲紅色或者黑色。

  2. 根節點必須是黑色的。

  3. 葉子節點都是黑色的 NULL 節點。

  4. 紅色節點的兩個子節點都是黑色(不容許兩個相鄰的紅色節點)。

  5. 從任意節點出發,到其每一個葉子節點的路徑中包含相同數量的黑色節點。

插入:60、5六、6八、4五、6四、5八、7二、4三、49

在這裏插入圖片描述 基於以上規則,能夠推導出:

從根節點到葉子節點的最長路徑(紅黑相間的路徑)不大於最短路徑(所有是黑色節點)的 2 倍。

  • 爲何不用紅黑樹?
  1. 只有兩路;
  2. 不夠平衡。
  3. 紅黑樹通常只放在內存裏面用。例如 Java 的 TreeMap。

索引方式:真的是用的 B+Tree 嗎?

在 Navicat 的工具中,建立索引,索引方式有兩種,Hash 和 B Tree。

HASH:以 KV 的形式檢索數據,也就是說,它會根據索引字段生成哈希碼和指針,指針指向數據。 在這裏插入圖片描述

  • 哈希索引有什麼特色呢?
  1. 它的時間複雜度是 O(1),查詢速度比較快。由於哈希索引裏面的數據不是按順序存儲的,因此不能用於排序。
  2. 咱們在查詢數據的時候要根據鍵值計算哈希碼,因此它只能支持等值查詢(= IN),不支持範圍查詢(> < >= <= between and)。
  3. 另一個就是若是字段重複值不少的時候,會出現大量的哈希衝突(採用拉鍊法解決),效率會下降。
  • 問題: InnoDB 能夠在客戶端建立一個索引,使用哈希索引嗎?

咱們先到官網看看介紹: https://dev.mysql.com/doc/refman/5.7/en/innodb-introduction.html InnoDB utilizes hash indexes internally for its Adaptive Hash Index feature 直接翻譯過來就是:InnoDB 內部使用哈希索引來實現自適應哈希索引特性。 這句話的意思是 InnoDB 只支持顯式建立 B+Tree 索引,對於一些熱點數據頁, InnoDB 會自動創建自適應 Hash 索引,也就是在 B+Tree 索引基礎上創建 Hash 索引,這個過程對於客戶端是不可控制的,隱式的。 咱們在 Navicat 工具裏面選擇索引方法是哈希,可是它建立的仍是 B+Tree 索引,這個不是咱們能夠手動控制的。 在 buffer pool 裏面有一塊區域是 Adaptive Hash Index 自適應哈希索引,就是指這個。

這個開關默認是 ON:

show variables like 'innodb_adaptive_hash_index';

從存儲引擎的運行信息中能夠看到:

show engine innodb status\G
----------------------
BUFFER POOL AND MEMORY
----------------------
-------------------------------------
INSERT BUFFER AND ADAPTIVE HASH INDEX
-------------------------------------

由於 B Tree 和 B+Tree 的特性,它們普遍地用在文件系統和數據庫中,例如Windows 的 HPFS 文件系統,Oracel、MySQL、SQLServer 數據庫。

B+Tree落地形式

MySQL 架構

經過上節課咱們知道,MySQL 是一個支持插件式存儲引擎的數據庫。在 MySQL 裏面,每一個表在建立的時候均可以指定它所使用的存儲引擎。

這裏咱們主要關注一下最經常使用的兩個存儲引擎,MyISAM 和 InnoDB 的索引的實現。

MySQL 數據存儲文件

首先,MySQL 的數據都是文件的形式存放在磁盤中的,咱們能夠找到這個數據目錄的地址。在 MySQL 中有這麼一個參數,咱們來看一下:

show VARIABLES LIKE 'datadir';

每一個數據庫有一個目錄,咱們新建了一個叫作 checkit 的數據庫,那麼這裏就有一個checkit 的文件夾。

這個數據庫裏面咱們又建了 5 張表:archive、innodb、memory、myisam、csv。

咱們進入 checkit 的目錄,發現這裏面有一些跟咱們建立的表名對應的文件。

在這裏咱們能看到,每張 InnoDB 的表有兩個文件(.frm 和.ibd),MyISAM 的表有三個文件(.frm、.MYD、.MYI)。

在這裏插入圖片描述 有一個是相同的文件,.frm。

.frm 是 MySQL 裏面表結構定義的文件,無論你建表的時候選用任何一個存儲引擎都會生成。

咱們主要看一下其餘兩個文件是怎麼實現 MySQL 不一樣的存儲引擎的索引的。

在 MyISAM 裏面,另外有兩個文件:

一個是.MYD 文件,D 表明 Data,是 MyISAM 的數據文件,存放數據記錄,好比咱們的 user_myisam 表的全部的表數據。

一個是.MYI 文件,I 表明 Index,是 MyISAM 的索引文件,存放索引,好比咱們在 id 字段上面建立了一個主鍵索引,那麼主鍵索引就是在這個索引文件裏面。

也就是說,在 MyISAM 裏面,索引和數據是兩個獨立的文件。

那咱們怎麼根據索引找到數據呢?

MyISAM 的 B+Tree 裏面,葉子節點存儲的是數據文件對應的磁盤地址。因此從索引文件.MYI 中找到鍵值後,會到數據文件.MYD 中獲取相應的數據記錄。 在這裏插入圖片描述 這裏是主鍵索引,若是是輔助索引,有什麼不同呢?

在 MyISAM 裏面,輔助索引也在這個.MYI 文件裏面。

輔助索引跟主鍵索引存儲和檢索數據的方式是沒有任何區別的,同樣是在索引文件

裏面找到磁盤地址,而後到數據文件裏面獲取數據。 在這裏插入圖片描述

再看看 innodb:

InnoDB 只有一個文件(.ibd 文件),那索引放在哪裏呢?

在 InnoDB 裏面,它是以主鍵爲索引來組織數據的存儲的,因此索引文件和數據文件是同一個文件,都在.ibd 文件裏面。

在 InnoDB 的主鍵索引的葉子節點上,它直接存儲了咱們的數據。

在這裏插入圖片描述

By the way

有問題?能夠給我留言或私聊 有收穫?那就順手點個讚唄~

固然,也能夠到個人公衆號下「6曦軒」,

回覆「學習」,便可領取一份 【Java工程師進階架構師的視頻教程】~

回覆「面試」,能夠得到: 【本人嘔心瀝血整理的 Java 面試題】

回覆「MySQL腦圖」,能夠得到 【MySQL 知識點梳理高清腦圖】

因爲我咧,科班出身的程序員,php,Android以及硬件方面都作過,不過最後仍是選擇專一於作 Java,因此有啥問題能夠到公衆號提問討論(技術情感傾訴均可以哈哈哈),看到的話會盡快回復,但願能夠跟你們共同窗習進步,關於服務端架構,Java 核心知識解析,職業生涯,面試總結等文章會不按期堅持推送輸出,歡迎你們關注~~~

在這裏插入圖片描述

相關文章
相關標籤/搜索