1. 程式人生 > 資料庫 >詳解MySQL回表查詢與索引覆蓋

詳解MySQL回表查詢與索引覆蓋

前言
  InnoDB引擎中,B+樹索引可以分為聚簇索引和輔助索引兩大類。在介紹 “回表” 和 “索引覆蓋” 之前,我們先來了解一下這兩個概念。

聚簇索引

  聚簇索引也叫聚集索引,它並不是一種單獨的索引型別,在聚簇索引的葉子頁中,儲存了整張表的行資料資訊,所以也將聚簇索引的葉子節點稱為資料頁。
  名詞 “聚簇” 表示資料行和相鄰的鍵值緊湊的儲存在一起。因為不能同時把資料行儲存在兩個不同的地方,所以一個表只能有一個聚簇索引。

InnoDB選取聚簇索引的規則如下:

  1. 如果表中定義了主鍵,則主鍵為聚簇索引;
  2. 如果沒有主鍵,選擇第一個非空的唯一索引為聚簇索引;
  3. 如果以上都沒有,InnoDB會隱式定義一個6位元組的rowid主鍵來作為聚簇索引。

輔助索引

  輔助索引也叫非聚簇索引、非聚集索引、二級索引等。輔助索引跟聚簇索引的區別在於,聚簇索引葉子節點中儲存了完整的行資料,而輔助索引葉子節點中儲存的是聚簇索引中的索引鍵值。
  輔助索引的存在不影響資料在聚簇索引中的組織,因此每張表中可以有多個輔助索引。


假如我們有這樣一個表,建表語句如下:

CREATE TABLE `t_user` (
  `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主鍵',
  `name` varchar(32) NOT NULL COMMENT '姓名',
  `age` tinyint(3) unsigned NOT NULL COMMENT '年齡',
  `gender` tinyint(3) unsigned NOT NULL COMMENT '性別:1男,0女',
  PRIMARY KEY (`id`),
  KEY `idx_name` (`name`,`age`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

表中資料:

idnameagegender
1LiLei181
2HanMeimei170
3Lucy170
4Lili160
5WeiHua320
6ZhangWei251
7Ann360
8Lisa190
9ZhangWei181
10Kate171

表中有兩個索引,一個是主鍵索引 id,一個是普通索引 (name,age),根據前邊介紹的聚簇索引和輔助索引的定義,這裡主鍵索引就是聚簇索引,普通索引就是我們的輔助索引。

兩棵索引樹的示意圖如下

在這裡插入圖片描述
在這裡插入圖片描述
回表查詢

那麼,什麼是回表查詢呢?

假如我們需要查詢姓名為Lucy的使用者資訊

select * from t_user where name = 'Lucy';

在這裡插入圖片描述
會先通過(name

,age)這課索引樹找到主鍵id(這裡是3),再根據id=3,回到主鍵索引樹中,找到對應的行資料資訊(“Lucy”,17,0),這個過程,就叫做 “回表查詢”。通過執行計劃,也能看到使用了輔助索引 idx_name

回表查詢需要掃描兩次索引樹,即先掃描輔助索引樹,再掃描聚簇索引樹,故它的效能比掃一遍索引樹低。以上邊的查詢為例,輔助索引樹高度為2,聚簇索引樹高度也為2,因此一共需要4次邏輯IO才能得到最終的資料頁。

索引覆蓋
  從上邊我們知道了,通過輔助索引查詢資料時,需要回到聚簇索引再掃描一遍,也就是需要 “回表查詢” 。那有沒有不需要回表查詢的情況呢?
  InnoDB儲存引擎支援 “索引覆蓋” (也叫做 “覆蓋索引” ),即從索引中就可以得到查詢結果,從而不需要查詢聚簇索引中的行資料資訊。

索引覆蓋可以帶來很多的好處:

  • 輔助索引不包含行資料的所有資訊,故其大小遠小於聚簇索引,因此可以減少大量的IO操作。
  • 索引覆蓋只需要掃描一次索引樹,不需要回表掃描聚簇索引樹,所以效能比回表查詢要高。
  • 索引中列值是按順序儲存的,索引覆蓋能避免範圍查詢回錶帶來的大量隨機IO操作。

判斷一條語句是否用到索引覆蓋

索引覆蓋有這麼多的好處,那平常開發中,我們怎麼知道語句是否用到了索引覆蓋呢?

我們來看下這條語句的執行計劃,

EXPLAIN select * from t_user where name = 'Lucy';

在這裡插入圖片描述
通過執行計劃,顯示用到了索引 idx_name ,也就是(name,age) 這兩欄位對應的輔助索引。

對這條語句做下修改,再來看下執行計劃

EXPLAIN select id,name from t_user where name = 'Lucy';

在這裡插入圖片描述
執行計劃中有了變化啊,最後一列Extra中多了 Using index 。而這裡Using index 就表示使用到了索引 , 並且所取的資料完全在索引中就能拿到,也就是用到了索引覆蓋。

這也容易理解,我們修改語句後,需要查詢的只有 id 和 name ,而這倆欄位在我們的輔助索引(name,age)樹中都有,name 就是索引鍵值的一部分,id儲存在葉子節點中,所以也就不需要再回表查詢了。

會用到索引覆蓋的SQL示例:

我們來看下這些例子

EXPLAIN select id,name,age from t_user where name = 'Lucy';
EXPLAIN select id,name,age from t_user where name = 'Lucy' and age = 17;
EXPLAIN select count(*) from t_user where name = 'Lucy';

這三條語句應該不難分析,name 就是索引鍵值的一部分,符合最左匹配原則,並且想要查詢的資料從索引樹中就能拿到。所以用到了索引覆蓋。

EXPLAIN select id,name,age from t_user where age = 17;
EXPLAIN select count(*) from t_user ;

上邊這兩條語句,也用到了索引覆蓋。

WHAT ?有同學可能就發現問題了,不對吧?第一條語句查詢條件 where age = 17 不符合最左匹配原則,沒辦法使用索引啊。第二條語句都沒有查詢條件,也沒辦法使用索引啊。

別急,我們先來看下執行計劃。
在這裡插入圖片描述
在這裡插入圖片描述
通過執行計劃,我們會發現它們的 possible_keys 這列都沒有值。執行計劃中, possible_keys 這一列表示的是可能用到的索引,而我們之前截圖中,這一列中都是有值的。但 key 這一列中都有值 idx_name, 並且 Extra 中也都有 Using index ,說明用到了索引覆蓋。

真實原因是這樣的。MySQL優化器分析發現,查詢語句無法使用到索引,只能通過全表掃描了。不過還發現一點,不管是隻掃描聚簇索引對應的表,還是隻掃描輔助索引對應的表,最終都能得到查詢結果。而輔助索引對應的表遠小於聚簇索引對應的表,這樣就可以減少IO操作,所以優化器就選擇了全表掃描輔助索引對應的表,也就用到了索引覆蓋。

EXPLAIN select id from t_user where id = '3';
EXPLAIN select count(*) from t_user where id = '3';

再繼續來看上邊這兩條語句。先來說下結論,這兩條也用到了索引覆蓋。

看下執行計劃

在這裡插入圖片描述
在這裡插入圖片描述
這兩條語句都是通過 id 來進行查詢,所以會用到主鍵索引,但是為什麼也會用到索引覆蓋呢?它們已經不需要回表了呀?

做出回答前,我們先再來看一條SQL執行計劃

EXPLAIN select name from t_user where id = '3';

在這裡插入圖片描述
這條語句跟前邊的差別只在於,前邊只查詢了 id,而這裡只查詢了 name 。執行計劃中就看到,這條查詢 name 的語句就沒有用到索引覆蓋。

我們再來體會一下:“索引覆蓋” 指的是,從索引中就可以得到查詢結果,從而不需要查詢聚簇索引中的行資料資訊。

也就是說,如果只查詢 ‘id’ 的話,從聚簇索引中就能得到結果,最終也不需要再查詢行資料資訊,也就是用到了索引覆蓋。同樣的,count(*) 也是這樣的原理。

總結

  索引的出現其實就是為了提高資料查詢的效率,就像書的目錄一樣。
  通常大家會根據查詢的WHERE條件來建立合適的索引,不過這只是索引優化的一個方面。設計優秀的索引應該考慮到整個查詢,而不單單是WHERE條件部分。

  最後,希望博主的文章能給大家帶來一些幫助。也希望能跟朋友們一起互相學習,共同進步,加油!!