1. 程式人生 > 其它 >什麼是 MySQL 的 回表 ?怎麼減少回表的次數?

什麼是 MySQL 的 回表 ?怎麼減少回表的次數?

什麼是 MySQL 的“回表”?怎麼減少回表的次數?

索引結構

要搞明白這個問題,需要大家首先明白 MySQL 中索引儲存的資料結構。這個其實很多小夥伴可能也都聽說過,B+Tree 嘛!

B+Tree 是什麼?那你得先明白什麼是 B-Tree,來看如下一張圖:

前面是 B-Tree,後面是 B+Tree,兩者的區別在於:

  1. B-Tree 中,所有節點都會帶有指向具體記錄的指標;B+Tree 中只有葉子結點會帶有指向具體記錄的指標。
  2. B-Tree 中不同的葉子之間沒有連在一起;B+Tree 中所有的葉子結點通過指標連線在一起。
  3. B-Tree 中可能在非葉子結點就拿到了指向具體記錄的指標,搜尋效率不穩定;B+Tree 中,一定要到葉子結點中才可以獲取到具體記錄的指標,搜尋效率穩定。

基於上面兩點分析,我們可以得出如下結論:

  1. B+Tree 中,由於非葉子結點不帶有指向具體記錄的指標,所以非葉子結點中可以儲存更多的索引項,這樣就可以有效降低樹的高度,進而提高搜尋的效率。
  2. B+Tree 中,葉子結點通過指標連線在一起,這樣如果有範圍掃描的需求,那麼實現起來將非常容易,而對於 B-Tree,範圍掃描則需要不停的在葉子結點和非葉子結點之間移動。

對於第一點,一個 B+Tree 可以存多少條資料呢?以主鍵索引的 B+Tree 為例(二級索引儲存資料量的計算原理類似,但是葉子節點和非葉子節點上儲存的資料格式略有差異),我們可以簡單算一下。

計算機在儲存資料的時候,最小儲存單元是扇區,一個扇區的大小是 512 位元組,而檔案系統(例如 XFS/EXT4)最小單元是塊,一個塊的大小是 4KB。InnoDB 引擎儲存資料的時候,是以頁為單位的,每個資料頁的大小預設是 16KB,即四個塊。

基於這樣的知識儲備,我們可以大致算一下一個 B+Tree 能存多少資料。

假設資料庫中一條記錄是 1KB,那麼一個頁就可以存 16 條資料(葉子結點);對於非葉子結點儲存的則是主鍵值+指標,在 InnoDB 中,一個指標的大小是 6 個位元組,假設我們的主鍵是 bigint ,那麼主鍵佔 8 個位元組,當然還有其他一些頭資訊也會佔用位元組我們這裡就不考慮了,我們大概算一下,小夥伴們心裡有數即可:

16*1024/(8+6)=1170

即一個非葉子結點可以指向 1170 個頁,那麼一個三層的 B+Tree 可以儲存的資料量為:

1170*1170*16=21902400

可以儲存 2100萬 條資料。

在 InnoDB 儲存引擎中,B+Tree 的高度一般為 2-4 層,這就可以滿足千萬級的資料的儲存,查詢資料的時候,一次頁的查詢代表一次 IO,那我們通過主鍵索引查詢的時候,其實最多隻需要 2-4 次 IO 操作就可以了。

大家先搞明白這個 B+Tree。

兩類索引

大家知道,MySQL 中的索引有很多中不同的分類方式,可以按照資料結構分,可以按照邏輯角度分,也可以按照物理儲存分,其中,按照物理儲存方式,可以分為聚簇索引和非聚簇索引。

我們日常所說的主鍵索引,其實就是聚簇索引(Clustered Index);主鍵索引之外,其他的都稱之為非主鍵索引,非主鍵索引也被稱為二級索引(Secondary Index),或者叫作輔助索引。

對於主鍵索引和非主鍵索引,使用的資料結構都是 B+Tree,唯一的區別在於葉子結點中儲存的內容不同:

  • 主鍵索引的葉子結點儲存的是一行完整的資料
  • 非主鍵索引的葉子結點儲存的則是主鍵值。葉子結點不包含行記錄的全部資料;非主鍵的葉子結點中,除了用來排序的key還包含一個bookmark;該書籤儲存了聚集索引的key。

這就是兩者最大的區別。

所以,當我們需要查詢的時候:

  1. 如果是通過主鍵索引來查詢資料,例如 select * from user where id=100,那麼此時只需要搜尋主鍵索引的 B+Tree 就可以找到資料。
  2. 如果是通過非主鍵索引來查詢資料,例如 select * from user where username='javaboy',那麼此時需要先搜尋 username 這一列索引的 B+Tree,搜尋完成後得到主鍵的值,然後再去搜索主鍵索引的 B+Tree,就可以獲取到一行完整的資料。

對於第二種查詢方式而言,一共搜尋了兩棵 B+Tree,第一次搜尋 B+Tree 拿到主鍵值後再去搜索主鍵索引的 B+Tree,這個過程就是所謂的回表。

從上面的分析中我們也能看出,通過非主鍵索引查詢要掃描兩棵 B+Tree,而通過主鍵索引查詢只需要掃描一棵 B+Tree,所以如果條件允許,還是建議在查詢中優先選擇通過主鍵索引進行搜尋。

眾所周知在InnoDB引用的是B+樹索引模型,這裡對B+樹結構暫時不做過多闡述,很多文章都有描述,在第二問中我們對索引的種類劃分為兩大類主鍵索引和非主鍵索引,那麼問題就在於比較兩種索引的區別了,我們這裡建立一張學生表,其中包含欄位id設定主鍵索引、name設定普通索引、age(無處理),並向資料庫中插入4條資料:("小趙", 10)("小王", 11)("小李", 12)("小陳", 13)

create table `student` (
  `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '自增主鍵',
  `name` varchar(32) COLLATE utf8_bin NOT NULL COMMENT '名稱',
  `age` int(3) unsigned NOT NULL DEFAULT '1' COMMENT '年齡',
  primary key (`id`),
  KEY `I_name` (`name`)
) ENGINE=InnoDB;

INSERT INTO student (name, age) VALUES("小趙", 10),("小王", 11),("小李", 12),("小陳", 13);

這裡我們設定了主鍵為自增,那麼此時資料庫裡資料為

每一個索引在 InnoDB 裡面對應一棵B+樹,那麼此時就存著兩棵B+樹。

可以發現區別在與葉子節點中主鍵索引儲存了整行資料,而非主鍵索引中儲存的值為主鍵id, 在我們執行如下sql後

SELECT age FROM student WHERE name = '小李';

流程為:

  1. name索引樹上找到名稱為小李的節點 id為 03
  2. id索引樹上找到id為 03的節點 獲取所有資料
  3. 從資料中獲取欄位命為age的值返回 12

在流程中從非主鍵索引樹搜尋回到主鍵索引樹搜尋的過程稱為:回表,在本次查詢中因為查詢結果只存在主鍵索引樹中,我們必須回表才能查詢到結果,那麼如何優化這個過程呢?引入正文覆蓋索引

覆蓋索引

就是把單列的非主鍵 索引 修改為 多欄位 的聯合索引, 在一棵索引數上。 就找到了想要的資料, 不需要去主鍵索引樹上,再檢索一遍 這個現象,稱之為 索引覆蓋.

覆蓋索引(covering index ,或稱為索引覆蓋)即從非主鍵索引中就能查到的記錄,而不需要查詢主鍵索引中的記錄,避免了回表的產生減少了樹的搜尋次數,顯著提升效能。

  • 如何使用是覆蓋索引?

之前我們已經建立了表student,那麼現在出現的業務需求中要求根據名稱獲取學生的年齡,並且該搜尋場景非常頻繁,那麼先在我們刪除掉之前以欄位name建立的普通索引,nameage兩個欄位建立聯合索引,sql命令與建立後的索引樹結構如下

# 刪除之前的非主鍵索引
alter table student drop index I_name;
# 新增非主鍵索引
alter table student add index I_name_age(name, age);

那在我們再次執行如下sql後:

select age from student where name = '小李';

流程為:

  1. name,age聯合索引樹上找到名稱為小李的節點
  2. 此時節點索引(非主鍵索引)裡包含資訊age 直接返回 12
  • 如何確定資料庫成功使用了覆蓋索引呢?

當發起一個索引覆蓋查詢時,在explainextra列可以看到using index的資訊:

這裡我們很清楚的看到Extrausing index表明我們成功使用了覆蓋索引。

覆蓋索引避免了回表現象的產生,從而減少樹的搜尋次數,顯著提升查詢效能,所以使用覆蓋索引是效能優化的一種手段。

  • 那麼不用主鍵索引就一定需要回表嗎?

不一定!

如果查詢的列本身就存在於索引中,那麼即使使用二級索引,一樣也是不需要回表的。

舉個例子,我有如下一張表:

unameaddress 欄位組成了一個複合索引,那麼此時,雖然這是一個非主鍵索引,但是索引樹的葉子節點中除了儲存主鍵值,也儲存了 address 的值。

我們來看如下分析:

explain select uname,address from user where uname='javaboy';

可以看到,此時使用到了 uname 索引,但是最後的 Extra 的值為 Using index,這就表示用到了索引覆蓋掃描(覆蓋索引),此時直接從索引中過濾不需要的記錄並返回命中的結果,這一步是在 MySQL 伺服器層完成的,並且不需要回表。

本文來自部落格園,作者:{BearBrick0},轉載請註明原文連結:{https://www.cnblogs.com/bearbrick0}