MySQL遵循最左前綴匹配原則!面試官:回去等通知吧
我們都知道,MySQL的Innodb引擎中,索引是通過B+樹來實現的。不管是普通索引還是聯合索引,都需要構造一個B+樹的索引結構。
那么,我們都知道普通索引的存儲結構中在B+樹的每個非節點上記錄的索引的值,而這棵B+樹的葉子節點上記錄的是聚簇索引(主鍵索引)的值。
如:
那么,如果是聯合索引的話,這棵B+樹又是如何存儲的呢?
在聯合索引中,聯合索引(name,age)也是一個B+樹,非葉子節點中記錄的是name,age兩個字段的值,葉子節點中記錄的是name,age兩個字段以及主鍵id的值。
?在存儲的過程中,如上圖所示,當age不同時,按照age排序,當age相同時,則按照name排序。
所以,了解了索引的存儲結構之后,我們就很容易理解最左前綴匹配了:因為索引底層是一個B+樹,如果是聯合索引的話,在構造B+樹的時候,會先按照左邊的key進行排序,左邊的key相同時再依次按照右邊的key排序。
所以,在通過索引查詢的時候,也需要遵守最左前綴匹配的原則,也就是需要從聯合索引的最左邊開始進行匹配,這時候就要求查詢語句的where條件中,包含最左邊的索引的值。
在了解了最左前綴匹配之后,日常我們在工作中,經常在簡歷索引以及查詢的時候,都會基于這個默認的約定進行索引的設計和SQL的優化。
大家都默認MySQL一定是遵循最左前綴匹配的。會認為當一個age,name的聯合索引存在時,如果查詢語句中不包含age作為條件,就一定不走索引。
MySQL一定是遵循最左前綴匹配的,這句話在以前是正確的,沒有任何毛病。但是在MySQL 8.0中,就不一定了。?
索引跳躍掃描
MySQL 8.0.13 版本中,對于range查詢(什么是range后面會提到),引入了索引跳躍掃描(Index Skip Scan)優化,支持不符合組合索引最左前綴原則條件下的SQL,依然能夠使用組合索引,減少不必要的掃描。
通過一個例子給大家解釋一下,首先有下面這樣一張表(參考了MySQL官網的例子,但是我做了些改動和優化):
通過上面的SQL,先創建一張t1表,并把f1,f2兩個字段設置為聯合索引。之后再向其中插入一些記錄。
分別在MySQL 5.7.9和MySQL 8.0.30上執行:
執行結果如下:
可以看到,主要有以下幾個區別:
MySQL 5.7中,type = index,rows=160,extra=Using where;Using index
MySQL 8.0中,type = range,rows=16,extra=Using where;Using index for skip scan
這里面的type指的是掃描方式,range表示的是范圍掃描,index表示的是索引樹掃描,通常情況下,range要比index快得多。
從rows上也能看得出來,使用index的掃描方式共掃描了160行,而使用range的掃描方式只掃描了16行。
?接著,重點來了,那就是為啥MySQL 8.0中的掃描方式可以更快呢?主要是因為Using index for skip scan 表示他用到了索引跳躍掃描的技術。
也就是說,雖然我們的SQL中,沒有遵循最左前綴原則,只使用了f2作為查詢條件,但是經過MySQL 8.0的優化以后,還是通過索引跳躍掃描的方式用到了索引了。?
優化原理
在MySQL 8.0.13 及以后的版本中,SELECT f1, f2 FROM t1 WHERE f2 = 40;SQL執行過程如下:
- 獲取f1字段第一個唯一值,也就是f1=1。
- 構造f1=1 and f2 = 40,進行范圍查詢。
- 獲取f1字段第二個唯一值,也就是f1=2。
- 構造f1=2 and f2 = 40,進行范圍查詢。
- 一直掃描完f1字段所有的唯一值,最后將結果合并返回。
也就是說,最終執行的SQL語句是像下面這樣的:?
即,MySQL的優化器幫我們把聯合索引中的f1字段作為查詢條件進行查詢了。
限制條件
?在知道了索引跳躍掃描的執行過程之后,很多聰明的讀者其實就會發現,這種查詢優化比較適合于f1的取值范圍比較少,區分度不高的情況,一旦f1的區分度特別高的話,這種查詢可能會更慢。
所以,真正要不要走索引跳躍掃描,還是要經過MySQL的優化器進行成本預估之后做決定的。
所以,這種優化一般用于那種聯合索引中第一個字段區分度不高的情況。但是話又說回來了,我們一般不太會把區分度不高的字段放在聯合索引的左邊,不過事無絕對,既然MySQL給了一個優化的方案,就說明還是有這樣的訴求的。
但是,我們不能依賴他這個優化,建立索引的時候,還是優先把區分度高的,查詢頻繁的字段放到聯合索引的左邊。?
除此之外,在MySQL官網中,還提到索引跳躍掃描還有一些其他的限制條件:
- 表T至少有一個聯合索引,但是對于聯合索引(A,B,C,D)來說,A和D可以是空的,但B和C必須是非空的。
- 查詢必須只能依賴一張表,不能多表JOIN。
- 查詢中不能使用GROUP BY或DISTINCT語句?。
- 查詢的字段必須是索引中的列。