1. 程式人生 > >MySQL單列索引和組合索引的選擇效率與explain分析

MySQL單列索引和組合索引的選擇效率與explain分析

一、先闡述下單列索引和組合索引的概念:

單列索引:即一個索引只包含單個列,一個表可以有多個單列索引,但這不是組合索引

組合索引:即一個索包含多個列。

如果我們的查詢where條件只有一個,我們完全可以用單列索引,這樣的查詢速度較快,索引也比較瘦身。如果我們的業務場景是需要經常查詢多個組合列,不要試圖分別基於單個列建立多個單列索引(因為雖然有多個單列索引,但是MySQL只能用到其中的那個它認為似乎最有效率的單列索引)。這是因為當SQL語句所查詢的列,全部都出現在複合索引中時,此時由於只需要查詢索引塊即可獲得所有資料,當然比使用多個單列索引要快得多。下面以實際例子說明:

舉例:

以下是程式碼片段:

CREATE TABLE people ( peopleid SMALLINT NOT NULL AUTO_INCREMENT, firstname CHAR(50) NOT NULL, lastname CHAR(50) NOT NULL, age SMALLINT NOT NULL, townid SMALLINT NOT NULL, PRIMARY KEY (peopleid) );

下面是我們插入到這個people表的資料:

這個資料片段中有四個名字為“Mikes”的人(其中兩個姓Sullivans,兩個姓McConnells),有兩個年齡為17歲的人,還有一個名字與眾不同的Joe Smith。

    這個表的主要用途是根據指定的使用者姓、名以及年齡返回相應的peopleid。例如,我們可能需要查詢姓名為Mike Sullivan、年齡17歲使用者的peopleid(SQL命令為SELECT peopleid FROM people WHERE firstname="Mike" AND lastname="Sullivan" AND age=17;)。由於我們不想讓MySQL每次執行查詢就去掃描整個表,這裡需要考慮運用索引。

 首先,我們可以考慮在單個列上建立索引,比如firstname、lastname或者age列。如果我們建立firstname列的索引(ALTER TABLE people ADD INDEX firstname (firstname);),MySQL將通過這個索引迅速把搜尋範圍限制到那些firstname="Mike"的記錄,然後再在這個“中間結果集”上進行其他條件的搜尋:它首先排除那些lastname不等於“Sullivan”的記錄,然後排除那些age不等於17的記錄。當記錄滿足所有搜尋條件之後,MySQL就返回最終的搜尋結果。

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

 為了提高搜尋效率,我們需要考慮運用多列索引。如果為firstname、lastname和age這三個列建立一個多列索引,MySQL只需一次檢索就能夠找出正確的結果!下面是建立這個多列索引的SQL命令:

以下是程式碼片段:
ALTER TABLE people ADD INDEX fname_lname_age (firstname,lastname,age);

  由於索引檔案以B+樹格式儲存,MySQL能夠立即轉到合適的firstname,然後再轉到合適的lastname,最後轉到合適的age。在沒有掃描資料檔案任何一個記錄的情況下,MySQL就正確地找出了搜尋的目標記錄!

   那麼,如果在firstname、lastname、age這三個列上分別建立單列索引,效果是否和建立一個firstname、lastname、age的多列索引一樣呢?答案是否定的,兩者完全不同。當我們執行查詢的時候,MySQL只能使用一個索引。如果你有三個單列的索引,MySQL會試圖選擇一個限制最嚴格的索引。但是,即使是限制最嚴格的單列索引,它的限制能力也肯定遠遠低於firstname、lastname、age這三個列上的多列索引。

二、謹防最左字首索引失效問題

繼續考慮前面的例子,現在我們有一個firstname、lastname、age列上的多列索引,我們稱這個索引為fname_lname_age。它相當於我們建立了(firstname,lastname,age)、(firstname,lastname)以及(firstname)這些列組合上的索引。為什麼沒有 (lastname,age)等這樣的組合索引呢?這是因為 mysql 組合索引"最左字首"(Leftmost Prefixing)的結果。簡單的理解就是隻從最左面的開始組合。並不是只要包含這三列的查詢都會用到該組合索引。

以下是程式碼片段:

The following queries can use the Leftmost Prefixing index:

SELECT peopleid FROM people WHERE firstname="Mike" AND lastname="Sullivan" AND age="17";

SELECT peopleid FROM people WHERE firstname="Mike" AND lastname="Sullivan";

SELECT peopleid FROM people WHERE firstname="Mike";

The following queries cannot use the Leftmost Prefixing index at all:

SELECT peopleid FROM people WHERE lastname="Sullivan";

SELECT peopleid FROM people WHERE age="17";

SELECT peopleid FROM people WHERE lastname="Sullivan" AND age="17";

這裡我特地實踐了下:

執行結果如下:

可以看出這裡最左字首索引失效了。這裡沒有用到索引直接全表掃描了。

我們再看下如果這樣呢?

執行結果如下:

可見還是用到了索引,這裡只是最左字首索引失效,但是不代表整個索引失效,只是效率沒有那麼高了。最左字首索引的效率是比較高的。本來我誤以為只要第一個查詢欄位不是組合索引的最左字首索引欄位整個索引會失效,其實不然。這裡強調下只是最有效率的最左字首索引失效不是整個索引失效。

三、使用explain分析索引

在不確定應該在哪些資料列上建立索引的時候,我們可以從EXPLAIN SELECT命令那裡往往可以獲得一些幫助。這其實只是簡單地給一條普通的SELECT命令加一個EXPLAIN關鍵字作為字首而已。有了這個關鍵字,MySQL將不是去執行那條SELECT命令,而是去對它進行分析。MySQL將以表格的形式把查詢的執行過程和用到的索引(如果有的話)等資訊列出來。這裡我基本闡述下每個資訊欄位含義,不展開闡述,我們只要注意幾個關鍵點(關鍵點以下用紅色加粗顯示)能大概看懂即可呵呵~~

1、id:SQL執行的順序的標識。


sql從裡向外執行,通過以上觀察發現sql是按照id從大到小執行的。

2、select_type: select型別

1)、SIMPLE(不使用UNION或子查詢等)

2) 、PRIMARY:最外層的select


3)、DERIVED:派生表的SELECT(FROM子句的子查詢)



4)、UNION:UNION中的第二個或後面的SELECT語句


5)、UNION RESULT:UNION的結果。



6)、DEPENDENT UNION:UNION中的第二個或後面的SELECT語句,取決於外面的查詢


7)、SUBQUERY:子查詢中的第一個SELECT


8)、DEPENDENT SUBQUERY:子查詢中的第一個SELECT,取決於外面的查詢

3、table:表的名字。

有時不是真實的表名字,看到的是derivedx(x是個數字,我的理解是第幾步執行的結果)

4、type:連線操作的型別。

這列很重要,顯示了連線使用了哪種類別,有無使用索引。在各種型別的關聯關係當中,效率最高的是system,然後依次是const、eq_ref、ref、range、index和 All。一般來說,得保證查詢至少達到range級別,最好能達到ref,否則就可能會出現效能問題。

1)、system  表只有一行:system表。這是const連線型別的特殊情況 2)、const  表中的一個記錄的最大值能夠匹配這個查詢(索引可以是主鍵或惟一索引)。因為只有一行,這個值實際就是常數,因為MYSQL先讀這個值然後把它當做常數來對待  3)、eq_ref  在連線中,MYSQL在查詢時,從前面的表中,對每一個記錄的聯合都從表中讀取一個記錄,它在查詢使用了索引為主鍵或惟一鍵的全部時使用  4)、ref 

這個連線型別只有在查詢使用了不是惟一或主鍵的鍵或者是這些型別的部分(比如,利用最左邊字首)時發生。對於之前的表的每一個行聯合,全部記錄都將從表中讀出。這個型別嚴重依賴於根據索引匹配的記錄多少(越少越好)

5)、range  這個連線型別使用索引返回一個範圍中的行,比如使用>或<查詢東西時發生的情況  6)、index  這個連線型別對前面的表中的每一個記錄聯合進行完全掃描(比ALL更好,因為索引一般小於表資料)  7)、ALL  這個連線型別對於前面的每一個記錄聯合進行完全掃描,這一般比較糟糕,應該儘量避免。因為它要掃描整個表。你可以加入更多的索引來解決這個問題。 5、possible_key:MySQL在搜尋資料記錄時可以選用的各個索引名。

這裡的索引名是建立索引時指定的索引暱稱;如果索引沒有暱稱,則預設顯示的是索引中第一個列的名字(在上一節舉的例子中是“firstname”)。預設索引名字的含義往往不是很明顯。

6、key:它顯示了MySQL實際使用的索引。

key資料列是MySQL實際選用的索引,如果它為空(或NULL),則MySQL不使用索引。

7、key_len:索引中被使用部分的長度,以位元組計。

key_len的值可以告訴你在聯合索引中mysql會真正使用了哪些索引。 在上例中,key_len是102,其中firstname佔50位元組,lastname佔50位元組,age佔2位元組(smallint儲存大小為2位元組)。如果MySQL只使用索引中的firstname部分,則key_len將是50。 在不損失精確性的情況下 ,key_len資料列裡的值越小越好(意思是更快)。

8、ref:顯示使用哪個列或常數與key一起從表中選擇行。

ref資料列給出了關聯關係中另一個數據表裡的資料列的名字。

9、rows:MySQL所認為的它在找到正確的結果之前必須掃描的記錄數。

顯然,這裡最理想的數字就是1。

10、extra:附加資訊

Using index和Using where會遇到的比較多,可以重點記下,其他的我沒怎麼遇到過了解即可,遇到具體問題可以查閱哈

1)、Distinct  一旦MYSQL找到了與行相聯合匹配的行,就不再搜尋了  2)、Not exists  MYSQL優化了LEFT JOIN,一旦它找到了匹配LEFT JOIN標準的行,就不再搜尋了  3)、Range checked for each  沒有找到理想的索引,因此對於從前面表中來的每一個行組合,MYSQL檢查使用哪個索引,並用它來從表中返回行。這是使用索引的最慢的連線之一  4)、Using filesort  看到這個的時候,查詢就需要優化了。MYSQL需要進行額外的步驟來發現如何對返回的行排序。它根據連線型別以及儲存排序鍵值和匹配條件的全部行的行指標來排序全部行  5)Using index  列資料是從僅僅使用了索引中的資訊而沒有讀取實際的行動的表返回的,這發生在對錶的全部的請求列都是同一個索引的部分的時候 6)Using temporary  看到這個的時候,查詢需要優化了。這裡,MYSQL需要建立一個臨時表來儲存結果,這通常發生在對不同的列集進行ORDER BY上,而不是GROUP BY上    7)Using where 使用了WHERE從句來限制哪些行將與下一張表匹配或者是返回給使用者。如果不想返回表中的全部行,並且連線型別ALL或index,這就會發生,或者是查詢有問題 -- 轉載自: