導讀:
? 首先創建一張 test 表,并插入一些數據:
CREATE TABLE `test` (
? `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主鍵',
? `a` int(11) NOT NULL,
? `b` int(11) NOT NULL,
? `c` int(11) NOT NULL,
? `d` int(11) NOT NULL,
? PRIMARY KEY (`id`),
? KEY `idx_abc` (`a`,`b`,`c`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
? 其中,test 表中有一個聯合索引 idx_abc,其包含索引(a)、索引(ab)、索引(abc)。而我們都知道聯合索引在使用時需要遵循最左匹配原則,也就是說,在執行 select * from test where a = 1 and b = 2 and c = 3 使用 索引(abc)、select * from test where a = 1 and b = 2 索引(ab),雖然使用到的索引列不同,但是歸根結底都使用的是 idx_abc 索引。
? 但是在執行 select * from test where a = 1 and b > 2 and c = 3 時,使用到的索引列僅僅為(ab),但是查詢條件中不是三個索引列都存在嗎,為什么只用到了兩個索引列呢?這個便是我們今天要討論的問題,即聯合索引中,為什么前一個索引列使用了范圍查詢后,下一個索引列將無法使用索引進行匹配?
一、最左匹配原則使用示例:
1、全值匹配查詢時
例如:
select * from test where a = 1 and b = 2 and c = 3;
select * fom test where b = 2 and a = 1 and c = 3;
select * from test where c = 3 and b = 2 and a = 1;
這些查詢都能用到索引 idx_abc,因為 MySQL 中有查詢優化器,會自動優化查詢順序,所以查詢條件中將索引列順序調換不影響聯合索引的使用。
2、匹配左邊的列時
例如:
select * from test where a = '1';
select * from test where a = '1' and b = '2';
select * from test where a = '1' and b = '2' and c = '3'
這些查詢都從最左邊開始連續匹配,用到了索引。
但如果查詢條件沒有從最左邊開始,則不會使用聯合索引,查詢會轉為全表掃描:
select * from test where b = '2';
select * from test where c = '3';
select * from test where b = '1' and c = '3'
這些沒有從最左邊開始,最后查詢沒有用到索引,用的是全表掃描。
select * from test where a = 1 and c = 3
部分索引,只有 a 列用到了索引,c 列無法使用索引。
3、匹配列前綴(%)
對于字符型字段的匹配:
- 如果查詢條件是前綴匹配(例如 a like 'As%'),可以使用索引,因為前綴是有序的。
- 如果查詢條件是后綴或中綴匹配(例如 a like '%As' 或 a like '%As%'),則不能使用索引,需要全表掃描。
例:
- select * from test where a like 'As%'(前綴匹配,走索引查詢)
- select * from test where a like '%As'(全表查詢)
- select * from test where a like '%As%'(全表查詢)
4、匹配范圍值
例:
- select * from test where a > 1 and a < 3:一個列進行范圍查詢,前綴匹配,走索引查詢。
- select * from test where a > 1 and a < 3 and b > 1:多個列同時進行范圍查找時。只有對索引最左邊的列進行范圍查找才用到 B+ 樹索引。因此,只有 a 列用到了索引,b 列無法使用索引,查詢會基于 1 < a < 3 的范圍查找記錄后,繼續逐條過濾。
5、精確匹配某一列并范圍匹配另外一列
例:
- select * from test where a = 1 and b > 3
在這種查詢中,如果左邊的列是精確查找,右邊的列可以進行范圍查找,可以進行范圍查找,聯合索引會加速查詢。
6、精確匹配某一列并范圍匹配另外一列,再精確或范圍匹配另外一列
例:
- select * from test where a = 1 and b > 3 and c = 2
在這種查詢中,聯合索引使用到的索引列仍然只有(a,b)。
解釋:
? MySQL 中的索引結構是 B+ 樹,葉子節點中的數據是以索引列從小到大的順序組織起來的,對于聯合索引來說,因為索引列不止有一列,所以數據的排列排列先按 a 列進行從小到大的排序,再按 b 列進行排序,最后按 c 列進行排序,對于上述 test 表中的數據,索引列 idx_abc 的葉子節點數據排序可以簡化為:
a | b | c |
1 | 2 | 4 |
1 | 2 | 8 |
1 | 6 | 2 |
1 | 6 | 3 |
2 | 2 | 7 |
2 | 7 | 4 |
3 | 2 | 4 |
3 | 2 | 6 |
3 | 3 | 2 |
3 | 3 | 8 |
? 可以看出,a 是有序的(構建一顆 B+ 樹只能根據一個值來構建,因此數據庫依據聯合索引最左的字段來構建 B+ 樹。),而 b,c 都是無序的。但是當 a 相同時,b 是有序的;當 b 相同時,c 又是有序的。
? 通過對聯合索引的結構的了解,那么就可以很好的了解為什么最左匹配原則中如果遇到范圍查詢就會停止了。以 select * from test where a=5 and b > 0 and c =1 為例,當查詢到 b 的值以后(這是一個范圍值),c 是無序的,所以就不能根據聯合索引來確定到底該取哪一行。
二、小結
根據上述使用示例,可以得出,最左匹配原則有以下特性:
- 最左優先:以最左邊的字段為起點,任何連續的索引都能匹配上。
- 范圍查詢字段后停止:當遇到 >、<、BETWEEN、LIKE之后,下一個列就不會再使用索引進行匹配。
三、索引下推:
? 在搞清楚"聯合索引中,前一個索引列使用了范圍查詢后,下一個索引列將無法使用索引進行快速定位"的問題后,我對上述示例6中的 SQL select * from test where a = 1 and b > 3 and c = 2; 進行了執行計劃分析,如下:
? 這個 Using index condition 就是我們接下來要討論的東西。
? 我們已知,上述 SQL 會使用到聯合索引 idx_abc 中的 a、b 索引列,但是 c 索引列不會使用索引進行匹配。那么對于 where 條件中的 c = 2 該怎么處理呢?
? 在 MySQL 5.6 之前,我們通過非聚簇索引 + 聚簇索引定位到一條記錄后,會將行數據返回給 Server 層,Server 層會根據 c = 2 對數據進行過濾,只要符合條件的記錄才會被返回給客戶端,此時的 Extra 中的信息應為 Using where。
? 但是從 MySQL 5.6 開始,引入了索引下推 (Index Condition Pushdown,ICP)來進行查詢優化,最主要的區別是,對于索引列 c 的處理不會再讓 Server 層去處理了,而是下推到存儲引擎層,即在對聯合索引進行數據匹配時,直接對使用不到索引的索引列條件進行判斷,最后的結果就是只對符合條件的記錄進行回表,這樣的話就可以大大減少回表的次數,從而提升查詢效率。
總結:
- 索引下推 (ICP) 是 MySQL 5.6 引入的一項關鍵的查詢優化技術。??
- ??核心思想:?? 將部分可以由索引包含的列完成的 WHERE 條件過濾操作,從服務器層“下推”到存儲引擎層執行。
- ??主要應用場景:?針對聯合索引 (INDEX (col1, col2, ...)) 的查詢,特別是當 WHERE 條件中:
- 查詢使用了索引的第一列(通常是范圍查詢:>, >=, <, <=, BETWEEN, LIKE 'a%'),并且
- 后面還包含其他索引列作為??等值(=)??或??范圍??條件 (col2 = X, col3 > Y, etc.)
- 可下推的條件:??下推的條件需要是??索引本身包含的列?? (稱為 Index Column)。在 idx_abc 例子中,c 是該索引的列,所以 c = 2 可以被下推。where a = 1 and b > 3 and c = 2 and d =7 中的 d = 7 因為 索引列不包含 b,所以其不能被下推。
- ??執行計劃標識:??使用 EXPLAIN 命令查看查詢執行計劃。如果看到 Extra 列顯示 ??Using index condition??,則說明優化器對該查詢使用了索引下推。
- ??巨大價值:??它允許存儲引擎在??回表讀取完整數據行之前??,就利用索引中存儲的后續列的值過濾掉大量不滿足所有條件的記錄,??顯著減少不必要的回表操作次數,從而大幅降低磁盤 I/O 和 CPU 開銷,提升查詢性能??。
舉例:
四、參考:
- MYSQL最左匹配原則及其底層邏輯-CSDN博客
- https://www.zhihu.com/question/52536048/answer/1906024782132125707