提到聯合索引的使用規則,一般我們都會想到左匹配原則,為什么是左不是右呢?這是因為即使是聯合索引在innodb底層也是使用b+樹來存放的,各個節點排序的規則就是按照聯合索引中多個字段從左往右依次排序的,所以查詢的時候需要左匹配才能保證b+樹中的數據是有序的,才能查詢;如果單獨查詢最右側字段,那么其在b+索引樹里面是完全無序的,自然也就無法查詢。
那么有這樣一個問題,架設聯合索引idx_a_b_c(a, b, c),其中針對a和c兩個字段進行查詢是否能夠使用索引呢?大家可以先暫停,自己思考一下~
相信大家經過思考已經有了自己的答案,那么我們一起去探索求證一下吧。
我這里使用的mysql版本為5.7.25
首先,我們創建一張表:將字段a,b,c設置為聯合索引。
create table `my_index_test` ( `id` int(11) unsigned not null auto_increment comment '主鍵id', `column_a` int(11) unsigned not null default 0 comment '字段a', `column_b` int(11) unsigned not null default 0 comment '字段b', `column_c` int(11) unsigned not null default 0 comment '字段c', `column_d` int(11) unsigned not null default 0 comment '字段d', primary key (`id`), key `idx_a_b_c` (`column_a`, `column_b`, `column_c`) ) ENGINE=InnoDB comment="聯合索引測試";
我們再插入一些測試數據
DELIMITER // CREATE PROCEDURE batch_insert() BEGIN DECLARE i INT DEFAULT 1; WHILE i <= 10000 DO INSERT INTO my_index_test(column_a,column_b,column_c,column_d) VALUES(i,i,i,i); SET i=i+1; END WHILE; END; // call batch_insert();
接下來我們分析一下查詢語句:
1. 首先我們來看常規的a,b,c查詢:
可以發現正常使用到了聯合索引,在我們的意料之中
2. 我們再來看一下b,c查詢:
這里大家要注意了,可能些同學看到explain語句分析的結果中key為idx_a_b_c就認為這條查詢語句使用到了索引,覺得效率很高,更重要的b,c查詢不符合左匹配的原則,為什么還會用到索引呢?這里稍微解釋一下type字段,在這個例子中我們看到type為index,它的意思是對索引進行全表掃描,在上面的例子中就是對idx_a_b_c索引進行全掃描,索引中的數據量是等於全表數據量的,換句話說type為index是另一種形式的全表掃描。
有同學可能會問那么innodb為什么會這么干呢?稍微在擴展一下:
這是因為我們select的字段"column_a"是存在於索引上,不需要回表再去查的。所以innodb引擎判斷直接在idx_a_b_c上進行全掃描即可完成任務,並且比在聚簇索引上進行全表掃描會更加節省內存,更加快速。如果我把語句改為“select column_d from my_index_test where column_b=3 and column_c = 1”,注意“column_d”字段不在任何索引上。
可以發現這次已經是type=all全表掃描了。有上述疑惑的同學們這下應該理解了吧~
好了我們言歸正傳,來驗證下最初的問題a,c會使用到索引嗎?
可以看到a,c查詢也是會用到idx_a_b_c索引的。
如何理解這個結果呢?開篇我們已經說過了聯合索引在innodb底層的存儲和排序方式:數據首先在a字段的維度進行排序,然后在b字段維度排序,最后是c字段,從左向右依次類推,最終存放在b+樹里。如果搜索條件是a,c的話,雖然c在結果區間是無序的(中間有一層b),但是單單是用a字段就可以把結果區間圈定在一個很小的范圍內,肯定是比全表掃描需要遍歷的字段要少對吧。
至於為什么網上去查資料很多都是a,c不支持的回答,想必是早期的innodb版本沒有把優化做到這一步,還有待看源碼確認,但是相信像能夠閱讀到這的優秀同學如果看到innodb居然不支持a,c使用聯合索引,也會想着親自去優化innodb的吧~