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