一、什么是回表查詢?
這先要從InnoDB的索引實現說起,InnoDB有兩大類索引:
- 聚集索引(clustered index)
- 普通索引(secondary index)
InnoDB聚集索引和普通索引有什么差異?
InnoDB聚集索引的葉子節點存儲行記錄,因此, InnoDB必須要有,且只有一個聚集索引:
(1)如果表定義了PK,則PK就是聚集索引;
(2)如果表沒有定義PK,則第一個not NULL unique列是聚集索引;
(3)否則,InnoDB會創建一個隱藏的row-id作為聚集索引;
所以PK查詢非常快,直接定位行記錄。
InnoDB普通索引的葉子節點存儲主鍵值。
注意,不是存儲行記錄頭指針,MyISAM的索引葉子節點存儲記錄指針。
舉個栗子,不妨設有表:
t(id PK, name KEY, sex, flag);
id是聚集索引,name是普通索引。
表中有四條記錄:
1
2
3
4
|
1
, shenjian, m, A
3
, zhangsan, m, A
5
, lisi, m, A
9
, wangwu, f, B
|
該聚集索引和普通索引如圖:
(1)id為PK,聚集索引,葉子節點存儲行記錄;
(2)name為KEY,普通索引,葉子節點存儲PK值,即id;
如圖可知主鍵索引從根節點開始利用頁目錄通過二分法查找某個索引頁,由於索引是有序的,所以在數據頁中同樣利用二分法查詢指定記錄。
普通索引和主鍵索引一樣是棵B+樹
,不過普通索引的記錄和頁按照某個非主鍵列
的值排序,葉子節點保存的不是完整數據,而是某個非主鍵列
和主鍵,普通索引的非葉結點保存的是非主鍵列
和頁號。
(重點)一個 SQL 只能利用到復合索引中的其中一列進行范圍查詢,因為B+樹的每個葉子節點有一個指針指向下一個節點,把某一索引列的所有的葉子節點串在了一起,只能根據單列的葉子節點進行范圍查詢,這就是復合索引中只能有其中一列使用索引進行范圍查詢的原理。
既然從普通索引無法直接定位行記錄,那普通索引的查詢過程是怎么樣的呢?
(重點)通常情況下,需要先遍歷普通索引的B+樹獲得聚集索引主鍵id,然后遍歷聚集索引的B+樹獲得行記錄的對應的值。
B+樹的每個葉子節點有一個指針指向下一個節點,把所有的葉子節點串在了一起,這就是范圍查詢使用索引的的原理。
1
|
select
*
from
t
where
name
=
'lisi'
;
|
如下圖所示流程:
如粉紅色路徑,需要掃碼兩遍索引樹:
(1)先通過普通索引定位到主鍵值id=5;
(2)在通過聚集索引定位到行記錄;
(重點)這就是所謂的回表查詢,先定位主鍵值,再定位行記錄,它的性能較掃一遍索引樹更低。
二、什么是索引覆蓋(Covering index)?
explain查詢計划優化章節,即explain的輸出結果Extra字段為Using index時,能夠觸發索引覆蓋。
只需要在一棵索引樹上就能獲取SQL所需的所有列數據,無需回表,速度更快。
三、如何實現索引覆蓋?
常見的方法是:將被查詢的字段,建立到聯合索引里去。
仍是《迅猛定位低效SQL》中的例子:
1
2
3
4
5
6
|
create
table
user
(
id
int
primary
key
,
name
varchar
(20),
sex
varchar
(5),
index
(
name
)
)engine=innodb;
|
第一個SQL語句:
select id,name from user where name='shenjian';
能夠命中name索引,索引葉子節點存儲了主鍵id,通過name的索引樹即可獲取id和name,無需回表,符合索引覆蓋,效率較高。
Extra:Using index。
第二個SQL語句:
1
|
select
id,
name
,sex
from
user
where
name
=
'shenjian'
;
|
能夠命中name索引,索引葉子節點存儲了主鍵id,但sex字段必須回表查詢才能獲取到,不符合索引覆蓋,需要再次通過id值掃描聚集索引獲取sex字段,效率會降低。
Extra:Using index condition。
如果把(name)單列索引升級為聯合索引(name, sex)就不同了。
1
2
3
4
5
6
|
create
table
user
(
id
int
primary
key
,
name
varchar
(20),
sex
varchar
(5),
index
(
name
, sex)
)engine=innodb;
|
可以看到:
select id,name ... where name='shenjian';
select id,name,sex ... where name='shenjian';
都能夠命中索引覆蓋,無需回表。
畫外音,Extra:Using index。
四、哪些場景可以利用索引覆蓋來優化SQL?
場景1:全表count查詢優化
原表為:
user(PK id, name, sex);
直接:
select count(name) from user;
不能利用索引覆蓋。
添加索引:
alter table user add key(name);
就能夠利用索引覆蓋提效。
場景2:列查詢回表優化
select id,name,sex ... where name='shenjian';
這個例子不再贅述,將單列索引(name)升級為聯合索引(name, sex),即可避免回表。
場景3:分頁查詢
select id,name,sex ... order by name limit 500,100;
將單列索引(name)升級為聯合索引(name, sex),也可以避免回表。
來源: