1. 程式人生 > >mysql索引最左匹配原則的理解?

mysql索引最左匹配原則的理解?

作者:沈傑
連結:https://www.zhihu.com/question/36996520/answer/93256153
來源:知乎
著作權歸作者所有。商業轉載請聯絡作者獲得授權,非商業轉載請註明出處。

這是你的表結構,有三個欄位,分別是id,name,cid
CREATE TABLE `student` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(255) DEFAULT NULL,
  `cid` int(11) DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `name_cid_INX` (`name`,`cid`),
) ENGINE=InnoDB AUTO_INCREMENT=8 DEFAULT CHARSET=utf8

索引方面:id是主鍵,(name,cid)是一個多列索引。
-----------------------------------------------------------------------------
下面是你有疑問的兩個查詢:

EXPLAIN SELECT * FROM student WHERE   cid=1;
<img src="https://pic1.zhimg.com/d3086a6c81bb2c77796cfc2249b610bc_b.png" data-rawwidth="1033" data-rawheight="60" class="origin_image zh-lightbox-thumb" width="1033" data-original="https://pic1.zhimg.com/d3086a6c81bb2c77796cfc2249b610bc_r.png">

EXPLAIN SELECT * FROM student WHERE   cid=1 AND name='小紅';
<img src="https://pic3.zhimg.com/53ab2cdea64b7e58e66c4ef86aa6b06a_b.png" data-rawwidth="1033" data-rawheight="49" class="origin_image zh-lightbox-thumb" width="1033" data-original="https://pic3.zhimg.com/53ab2cdea64b7e58e66c4ef86aa6b06a_r.png">


你的疑問是:sql查詢用到索引的條件是必須要遵守最左字首原則,為什麼上面兩個查詢還能用到索引?
---------------------------------------------------------------------------------------------------------------------------

講上面問題之前,我先補充一些知識,因為我覺得你對索引理解是狹隘的:
上述你的兩個查詢的explain結果中顯示用到索引的情況型別是不一樣的。,可觀察explain結果中的type欄位。你的查詢中分別是:
1. type: index
2. type: ref

解釋:
index:這種型別表示是mysql會對整個該索引進行掃描。要想用到這種型別的索引,對這個索引並無特別要求,只要是索引,或者某個複合索引的一部分,mysql都可能會採用index型別的方式掃描。但是呢,缺點是效率不高,mysql會從索引中的第一個資料一個個的查詢到最後一個數據,直到找到符合判斷條件的某個索引。

所以:對於你的第一條語句:
EXPLAIN SELECT * FROM student WHERE   cid=1;

判斷條件是cid=1,而cid是(name,cid)複合索引的一部分,沒有問題,可以進行index型別的索引掃描方式。explain顯示結果使用到了索引,是index型別的方式。

---------------------------------------------------------------------------------------------------------------------------

ref:這種型別表示mysql會根據特定的演算法快速查詢到某個符合條件的索引,而不是會對索引中每一個數據都進行一 一的掃描判斷,也就是所謂你平常理解的使用索引查詢會更快的取出資料。而要想實現這種查詢,索引卻是有要求的,要實現這種能快速查詢的演算法,索引就要滿足特定的資料結構。簡單說,也就是索引欄位的資料必須是有序的,才能實現這種型別的查詢,才能利用到索引。


有些瞭解的人可能會問,索引不都是一個有序排列的資料結構麼。不過答案說的還不夠完善,那只是針對單個索引,而複合索引的情況有些同學可能就不太瞭解了。

下面就說下複合索引:
以該表的(name,cid)複合索引為例,它內部結構簡單說就是下面這樣排列的:
<img src="https://pic2.zhimg.com/8c45fe417afbe97127e8c55fe1cd9395_b.png" data-rawwidth="149" data-rawheight="205" class="content_image" width="149">mysql建立複合索引的規則是首先會對複合索引的最左邊的,也就是第一個name欄位的資料進行排序,在第一個欄位的排序基礎上,然後再對後面第二個的cid欄位進行排序。其實就相當於實現了類似 order by name cid這樣一種排序規則。

mysql建立複合索引的規則是首先會對複合索引的最左邊的,也就是第一個name欄位的資料進行排序,在第一個欄位的排序基礎上,然後再對後面第二個的cid欄位進行排序。其實就相當於實現了類似 order by name cid這樣一種排序規則。

所以:第一個name欄位是絕對有序的,而第二欄位就是無序的了。所以通常情況下,直接使用第二個cid欄位進行條件判斷是用不到索引的,當然,可能會出現上面的使用index型別的索引。這就是所謂的mysql為什麼要強調最左字首原則的原因。

那麼什麼時候才能用到呢?
當然是cid欄位的索引資料也是有序的情況下才能使用咯,什麼時候才是有序的呢?觀察可知,當然是在name欄位是等值匹配的情況下,cid才是有序的。發現沒有,觀察兩個name名字為 c 的cid欄位是不是有序的呢。從上往下分別是4 5。
這也就是mysql索引規則中要求複合索引要想使用第二個索引,必須先使用第一個索引的原因。(而且第一個索引必須是等值匹配)。
---------------------------------------------------------------------------------------------------------------------------
所以對於你的這條sql查詢:
EXPLAIN SELECT * FROM student WHERE   cid=1 AND name='小紅';

沒有錯,而且複合索引中的兩個索引欄位都能很好的利用到了!因為語句中最左面的name欄位進行了等值匹配,所以cid是有序的,也可以利用到索引了。

你可能會問:我建的索引是(name,cid)。而我查詢的語句是cid=1 AND name='小紅'; 我是先查詢cid,再查詢name的,不是先從最左面查的呀?

好吧,我再解釋一下這個問題:首先可以肯定的是把條件判斷反過來變成這樣 name='小紅' and cid=1; 最後所查詢的結果是一樣的。
那麼問題產生了?既然結果是一樣的,到底以何種順序的查詢方式最好呢?

所以,而此時那就是我們的mysql查詢優化器該登場了,mysql查詢優化器會判斷糾正這條sql語句該以什麼樣的順序執行效率最高,最後才生成真正的執行計劃。所以,當然是 我們能儘量的利用到索引時的查詢順序效率最高咯,所以mysql查詢優化器會最終以這種順序進行查詢執行。

B-Tree 索引和 Hash 索引的對比 
對於 B-tree 和 hash 資料結構的理解能夠有助於預測不同儲存引擎下使用不同索引的查詢效能的差異,尤其是那些允許你選擇 B-tree 或者 hash 索引的記憶體儲存引擎。

B-Tree 索引的特點

B-tree 索引可以用於使用 =, >, >=, <, <= 或者 BETWEEN 運算子的列比較。如果 LIKE 的引數是一個沒有以萬用字元起始的常量字串的話也可以使用這種索引。

有時,即使有索引可以使用,MySQL 也不使用任何索引。發生這種情況的場景之一就是優化器估算出使用該索引將要求 MySql 去訪問這張表的絕大部分記錄。這種情況下,一個表掃描可能更快,因為它要求更少量的查詢。但是,如果這樣的一個查詢使用了 LIMIT 來檢索只是少量的記錄時,MySql 還是會使用索引,因為它能夠更快地找到這點記錄並將其返回。

Hash 索引的特點

Hash 索引有著與剛才所討論特點的相比截然不同的特點: 
Hash 索引只能夠用於使用 = 或者 <=> 運算子的相等比較(但是速度更快)。Hash 索引不能夠用於諸如 < 等用於查詢一個範圍值的比較運算子。依賴於這種單值查詢的系統被稱為 “鍵-值儲存”;對於這種系統,儘可能地使用 hash 索引。 
優化器不能夠使用 hash 索引來加速 ORDER BY 操作。這種型別的索引不能夠用於按照順序查詢下一個條目。 
MySql 無法使用 hash 索引估計兩個值之間有多少行(這種情況由範圍優化器來決定使用哪個索引)。如果你將一張 MyISAM 或 InnoDB 錶轉換成一個 hash 索引的記憶體表時,一些查詢可能會受此影響。 
查詢某行記錄必須進行全鍵匹配。而 B-tree 索引,任何該鍵的左字首都可用以查詢記錄。

最左字首原則

通過例項理解單列索引、多列索引以及最左字首原則

例項:現在我們想查出滿足以下條件的使用者id: 
mysql>SELECT `uid` FROM people WHERE lname`=’Liu’ AND `fname`=’Zhiqun’ AND `age`=26 
因為我們不想掃描整表,故考慮用索引。

單列索引: 
ALTER TABLE people ADD INDEX lname (lname); 
將lname列建索引,這樣就把範圍限制在lname=’Liu’的結果集1上,之後掃描結果集1,產生滿足fname=’Zhiqun’的結果集2,再掃描結果集2,找到 age=26的結果集3,即最終結果。

由 於建立了lname列的索引,與執行表的完全掃描相比,效率提高了很多,但我們要求掃描的記錄數量仍舊遠遠超過了實際所需 要的。雖然我們可以刪除lname列上的索引,再建立fname或者age 列的索引,但是,不論在哪個列上建立索引搜尋效率仍舊相似。

2.多列索引: 
ALTER TABLE people ADD INDEX lname_fname_age (lame,fname,age); 
為了提高搜尋效率,我們需要考慮運用多列索引,由於索引檔案以B-Tree格式儲存,所以我們不用掃描任何記錄,即可得到最終結果。

注:在mysql中執行查詢時,只能使用一個索引,如果我們在lname,fname,age上分別建索引,執行查詢時,只能使用一個索引,mysql會選擇一個最嚴格(獲得結果集記錄數最少)的索引。

3.最左字首:顧名思義,就是最左優先,上例中我們建立了lname_fname_age多列索引,相當於建立了(lname)單列索引,(lname,fname)組合索引以及(lname,fname,age)組合索引。

注:在建立多列索引時,要根據業務需求,where子句中使用最頻繁的一列放在最左邊。

拓展:在網上看到一個關於最左字首原則提出這麼一個例子。

多列欄位做索引,state/city/zipCode,想要索引生效的話,只能使用如下的組合 
state/city/zipCode 
state/city 
state 
其他方式(如city,city/zipCode),則索引不會生效 
這種現象是怎麼導致的?和索引的儲存方式有關嗎?

本人頁參考了下其他網友的觀點,個人認為,所謂最左字首原則就是先要看第一列,在第一列滿足的條件下再看左邊第二列,以此類推。有位網友描述得很形象: 
你可以認為聯合索引是闖關遊戲的設計

例如你這個聯合索引是state/city/zipCode

那麼state就是第一關 city是第二關, zipCode就是第三關

你必須匹配了第一關,才能匹配第二關,匹配了第一關和第二關,才能匹配第三關

你不能直接到第二關的

索引的格式就是第一層是state,第二層才是city

索引是因為B+樹結構 所以查詢快 如果單看第三列 是非排序的。 
多列索引是先按照第一列進行排序,然後在第一列排好序的基礎上再對第二列排序,如果沒有第一列的話,直接訪問第二列,那第二列肯定是無序的,直接訪問後面的列就用不到索引了。 
所以如果不是在前面列的基礎上而是但看後面某一列,索引是失效的。大家有不同的觀點可以提出,這是個人理解的觀點。

sql優化