【MySQL】InnoDB 的索引模型(B+樹)

目錄

一、InnoDB 的索引模型

二、索引維護

三、Mysql-Innodb索引二次查找解決方案

3.1 爲什麼會造成二級查找

3.2 解決方案

3.2.1 索引覆蓋

3.2.2 延時關聯

四、小結


一、InnoDB 的索引模型

在 MySQL 中,索引是在存儲引擎層實現的,所以並沒有統一的索引標準,即不同存儲引擎的索引的工作方式並不一樣。而即使多個存儲引擎支持同一種類型的索引,其底層的實現也可能不同。由於 InnoDB 存儲引擎在 MySQL 數據庫中使用最爲廣泛,所以下面我就以 InnoDB 爲例,和你分析一下其中的索引模型。

 

在 InnoDB 中,表都是根據主鍵順序以索引的形式存放的,這種存儲方式的表稱爲索引組織表。又因爲前面我們提到的,InnoDB 使用了 B+ 索引模型,所以數據都是存儲在 B+ 樹中的。

 

每一個索引在 InnoDB 裏面對應一棵 B+ 樹。

 

假設,我們有一個主鍵列爲 ID 的表,表中有字段 k,並且在 k 上有索引。

這個表的建表語句是:

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

表中 R1~R5 的 (ID,k) 值分別爲 (100,1)、(200,2)、(300,3)、(500,5) 和 (600,6),兩棵樹的示例示意圖如下。

InnoDB 的索引組織結構

從圖中不難看出,根據葉子節點的內容,索引類型分爲主鍵索引非主鍵索引

  • 主鍵索引的葉子節點存的是整行數據。在 InnoDB 裏,主鍵索引也被稱爲聚簇索引(clustered index
  • 非主鍵索引的葉子節點內容是主鍵的值。在 InnoDB 裏,非主鍵索引也被稱爲二級索引(secondary index

 

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

  • 如果語句是 select * from T where ID=500,即主鍵查詢方式,則只需要搜索 ID 這棵 B+ 樹;
  • 如果語句是 select * from T where k=5,即普通索引查詢方式,則需要先搜索 k 索引樹,得到 ID 的值爲 500,再到 ID 索引樹搜索一次。這個過程稱爲回表

 也就是說,基於非主鍵索引的查詢需要多掃描一棵索引樹。因此,我們在應用中應該儘量使用主鍵查詢。

 

二、索引維護

B+ 樹爲了維護索引有序性,在插入新值的時候需要做必要的維護。以上面這個圖爲例,如果插入新的行 ID 值爲 700,則只需要在 R5 的記錄後面插入一個新記錄。如果新插入的 ID 值爲 400,就相對麻煩了,需要邏輯上挪動後面的數據,空出位置

 

而更糟的情況是,如果 R5 所在的數據頁已經滿了(即一個數據頁中存儲的索引數據超過了規定的度數),根據 B+ 樹的算法,這時候需要申請一個新的數據頁,然後挪動部分數據過去。這個過程稱爲頁分裂。在這種情況下,性能自然會受影響。

 

除了性能外,頁分裂操作還影響數據頁的利用率。原本放在一個頁的數據,現在分到兩個頁中,整體空間利用率降低大約 50%。

 

當然有分裂就有合併。當相鄰兩個頁由於刪除了數據,利用率很低之後,會將數據頁做頁合併。合併的過程,可以認爲是分裂過程的逆過程

 

基於上面的索引維護過程說明,我們來討論一個案例:

 

你可能在一些建表規範裏面見到過類似的描述,要求建表語句裏一定要有自增主鍵。當然事無絕對,我們來分析一下哪些場景下應該使用自增主鍵,而哪些場景下不應該

 

自增主鍵是指自增列上定義的主鍵,在建表語句中一般是這麼定義的:

NOT NULL PRIMARY KEY AUTO_INCREMENT

插入新記錄的時候可以不指定 ID 的值,系統會獲取當前 ID 最大值加 1 作爲下一條記錄的 ID 值。

 

也就是說,自增主鍵的插入數據模式,正符合了我們前面提到的遞增插入的場景。每次插入一條新記錄,都是追加操作,都不涉及到挪動其他記錄,也不會觸發葉子節點的分裂。

 

而有業務邏輯的字段做主鍵,則往往不容易保證有序插入,這樣寫數據成本相對較高(也就是上面涉及到的插入數據時出現的性能損耗)。

 

除了考慮性能外,我們還可以從存儲空間的角度來看。假設你的表中確實有一個唯一字段,比如字符串類型的身份證號,那應該用身份證號做主鍵,還是用自增字段做主鍵呢?

 

由於每個非主鍵索引的葉子節點上都是主鍵的值。如果用身份證號做主鍵,那麼每個二級索引的葉子節點佔用約 20 個字節,而如果用整型做主鍵,則只要 4 個字節,如果是長整型(bigint)則是 8 個字節。

 

顯然,主鍵長度越小,普通索引的葉子節點就越小,普通索引佔用的空間也就越小

 

所以,從性能和存儲空間方面考量,自增主鍵往往是更合理的選擇

 

有沒有什麼場景適合用業務字段直接做主鍵的呢?還是有的。比如,有些業務的場景需求是這樣的:

  1. 只有一個索引;
  2. 該索引必須是唯一索引。

 

你一定看出來了,這就是典型的 KV 場景。

 

由於沒有其他索引,所以也就不用考慮其他索引的葉子節點大小的問題。

 

這時候我們就要優先考慮上一段提到的“儘量使用主鍵查詢”原則,直接將這個索引設置爲主鍵,可以避免每次查詢需要搜索兩棵樹。

 

三、Mysql-Innodb索引二次查找解決方案

3.1 爲什麼會造成二級查找

  因爲Innodb二級索引存儲的是主鍵,所以通過索引查找時,第一次查詢是通過二級索引找到主鍵值,第二次查詢是通過主鍵在聚簇索引找到對應的行位置

 

3.2 解決方案

3.2.1 索引覆蓋

一個包含查詢所需的字段的索引稱爲 covering index 覆蓋索引。MySQL只需要通過索引就可以返回查詢所需要的數據,而不必在查到索引之後進行回表操作,減少IO,提供效率。

select index_column from table 1

只要讓二級索引包含了要查詢的字段,則不需要再去聚簇索引進行二次查找

 

3.2.2 延時關聯

延遲關聯:通過使用覆蓋索引查詢返回需要的主鍵,再根據主鍵關聯原表獲得需要的數據。

select * from orders o 
inner join 
(select order_id from orders  where addtime = '') o1 
ON o.order_id = o1.order_id;

在優化分頁查詢時用到了延時關聯提高查詢效率

select * from orders o1 
inner join 
(select order_id from orders order by addtime limit 1902,20) o2 
ON o1.order_id = o2.order_id

 

四、小結

今天,我跟你分析了數據庫引擎可用的數據結構,介紹了 InnoDB 採用的 B+ 樹結構,以及爲什麼 InnoDB 要這麼選擇。B+ 樹能夠很好地配合磁盤的讀寫特性,減少單次查詢的磁盤訪問次數。儘量將B+樹的數據頁大小設置的和磁盤的頁大小一致,可以有效減少IO次數(季計算機存儲器是通過頁來管理的,操作系統中學的)

由於 InnoDB 是索引組織表,一般情況下我會建議你創建一個自增主鍵,這樣非主鍵索引佔用的空間最小。但事無絕對,我也跟你討論了使用業務邏輯字段做主鍵的應用場景。


其他相關文章:【MySQL】MySQL的存儲引擎和索引詳解(聚集索引和非聚集索引)
                        【MySQL】InnoDB行格式、數據頁結構以及索引底層原理分析
                        【MySQL】InnoDB存儲引擎,MyISAM存儲引擎,聚集索引,非聚集索引,主鍵索引,二級索引他們之間的關係梳理
                        【MySQL】MySQL的鎖與事務隔離級別詳解
                        【MySQL】MySQL分庫分表詳解
                        【MySQL】主從複製實現原理詳解


參考資料:《MySQL實戰45講》林曉斌
                  高性能MySQL》(第3版)

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