詳解MySQL回表查詢與索引覆蓋
前言
InnoDB引擎中,B+樹索引可以分為聚簇索引和輔助索引兩大類。在介紹 “回表” 和 “索引覆蓋” 之前,我們先來了解一下這兩個概念。
聚簇索引
聚簇索引也叫聚集索引,它並不是一種單獨的索引型別,在聚簇索引的葉子頁中,儲存了整張表的行資料資訊,所以也將聚簇索引的葉子節點稱為資料頁。
名詞 “聚簇” 表示資料行和相鄰的鍵值緊湊的儲存在一起。因為不能同時把資料行儲存在兩個不同的地方,所以一個表只能有一個聚簇索引。
InnoDB選取聚簇索引的規則如下:
- 如果表中定義了主鍵,則主鍵為聚簇索引;
- 如果沒有主鍵,選擇第一個非空的唯一索引為聚簇索引;
- 如果以上都沒有,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;
表中資料:
id | name | age | gender |
---|---|---|---|
1 | LiLei | 18 | 1 |
2 | HanMeimei | 17 | 0 |
3 | Lucy | 17 | 0 |
4 | Lili | 16 | 0 |
5 | WeiHua | 32 | 0 |
6 | ZhangWei | 25 | 1 |
7 | Ann | 36 | 0 |
8 | Lisa | 19 | 0 |
9 | ZhangWei | 18 | 1 |
10 | Kate | 17 | 1 |
表中有兩個索引,一個是主鍵索引 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條件部分。
最後,希望博主的文章能給大家帶來一些幫助。也希望能跟朋友們一起互相學習,共同進步,加油!!