MySQL Explain詳解,添加索引sql優化

EXPLAIN語法(獲取SELECT相關信息)

EXPLAIN tbl_name

或:

EXPLAIN [EXTENDED] SELECT select_options

當我們使用select查詢時發現很慢,所有通過我們常常用到explain這個命令來查看一個這些SQL語句的執行計劃,查看該SQL語句有沒有使用上了索引,有沒有做全表掃描,這都可以通過explain命令來查看。

EXPLAIN為用於SELECT語句中的每個表返回一行信息。表以它們在處理查詢過程中將被MySQL讀入的順序被列出。MySQL用一遍掃描多次聯接(single-sweep multi-join)的方式解決所有聯接。這意味著MySQL從第一個表中讀一行,然後找到在第二個表中的一個匹配行,然後在第3個表中等等。當所有的表處理完後,它輸出選中的列並且返回表清單直到找到一個有更多的匹配行的表。從該表讀入下一行並繼續處理下一個表。

/*實際SQL,查找用戶名為小米的員工*/
SELECT * FROM a WHERE NAME ="小米";
-- 查看SQL是否使用索引,前面加上explain即可
EXPLAIN SELECT * FROM a WHERE NAME ="小米";
MySQL Explain詳解,添加索引sql優化

EXPLAIN出來的信息有10列,分別是id、select_type、table、partitions ,type、possible_keys、key、key_len、ref、rows、filtered,Extra

EXPLAIN的每個輸出行提供一個表的相關信息,並且每個行包括下面的列:

1>> id

SELECT識別符。這是SELECT的查詢序列號。

2>> select_type

SELECT類型,可以為以下任何一種:

  • SIMPLE

簡單SELECT(不使用UNION或子查詢)

  • PRIMARY

最外面的SELECT

  • UNION

UNION中的第二個或後面的SELECT語句

  • DEPENDENT UNION

UNION中的第二個或後面的SELECT語句,取決於外面的查詢

  • UNION RESULT

UNION的結果。

  • SUBQUERY

子查詢中的第一個SELECT

  • DEPENDENT SUBQUERY

子查詢中的第一個SELECT,取決於外面的查詢

  • DERIVED

導出表的SELECT(FROM子句的子查詢)

3>> table

輸出的行所引用的表。

4>> type

聯接類型。下面給出各種聯接類型,按照從最佳類型到最壞類型進行排序:

system>const>eq_ref>ref>ref_or_null>index_merge>unique_subquery>index_subquery>range>index>ALL

  • system

表僅有一行(=系統表)。這是const聯接類型的一個特例。

  • const

表最多有一個匹配行,它將在查詢開始時被讀取。因為僅有一行,在這行的列值可被優化器剩餘部分認為是常數。const表很快,因為它們只讀取一次!

const用於用常數值比較PRIMARY KEY或UNIQUE索引的所有部分時。在下面的查詢中,tbl_name可以用於const表:

SELECT * from tbl_name WHERE primary_key=1;
 
SELECT * from tbl_name
WHERE primary_key_part1=1和 primary_key_part2=2;
MySQL Explain詳解,添加索引sql優化

EXPLAIN SELECT * FROM a WHERE id=4;
MySQL Explain詳解,添加索引sql優化

  • eq_ref

對於每個來自於前面的表的行組合,從該表中讀取一行。這可能是最好的聯接類型,除了const類型。它用在一個索引的所有部分被聯接使用並且索引是UNIQUE或PRIMARY KEY。

eq_ref可以用於使用= 操作符比較的帶索引的列。比較值可以為常量或一個使用在該表前面所讀取的表的列的表達式。

在下面的例子中,MySQL可以使用eq_ref聯接來處理ref_tables:

SELECT * FROM ref_table,other_table
 WHERE ref_table.key_column=other_table.column;
 
SELECT * FROM ref_table,other_table
 WHERE ref_table.key_column_part1=other_table.column
 AND ref_table.key_column_part2=1;
  • ref

對於每個來自於前面的表的行組合,所有有匹配索引值的行將從這張表中讀取。如果聯接只使用鍵的最左邊的前綴,或如果鍵不是UNIQUE或PRIMARY KEY(換句話說,如果聯接不能基於關鍵字選擇單個行的話),則使用ref。如果使用的鍵僅僅匹配少量行,該聯接類型是不錯的。

ref可以用於使用=或<=>操作符的帶索引的列。

在下面的例子中,MySQL可以使用ref聯接來處理ref_tables:

SELECT * FROM ref_table WHERE key_column=expr;
 
SELECT * FROM ref_table,other_table
 WHERE ref_table.key_column=other_table.column;
 
SELECT * FROM ref_table,other_table
 WHERE ref_table.key_column_part1=other_table.column
 AND ref_table.key_column_part2=1;
  • ref_or_null

該聯接類型如同ref,但是添加了MySQL可以專門搜索包含NULL值的行。在解決子查詢中經常使用該聯接類型的優化。

在下面的例子中,MySQL可以使用ref_or_null聯接來處理ref_tables:

SELECT * FROM ref_table
WHERE key_column=expr OR key_column IS NULL;
  • index_merge

該聯接類型表示使用了索引合併優化方法。在這種情況下,key列包含了使用的索引的清單,key_len包含了使用的索引的最長的關鍵元素。詳細信息參見7.2.6節,“索引合併優化”。

  • unique_subquery

該類型替換了下面形式的IN子查詢的ref:

value IN (SELECT primary_key FROM single_table WHERE some_expr)

unique_subquery是一個索引查找函數,可以完全替換子查詢,效率更高。

  • index_subquery

該聯接類型類似於unique_subquery。可以替換IN子查詢,但只適合下列形式的子查詢中的非唯一索引:

value IN (SELECT key_column FROM single_table WHERE some_expr)
  • range

只檢索給定範圍的行,使用一個索引來選擇行。key列顯示使用了哪個索引。key_len包含所使用索引的最長關鍵元素。在該類型中ref列為NULL。

當使用=、<>、>、>=、、BETWEEN或者IN操作符,用常量比較關鍵字列時,可以使用range:

SELECT * FROM tbl_name
WHERE key_column = 10;
 
SELECT * FROM tbl_name
WHERE key_column BETWEEN 10 and 20;
 
SELECT * FROM tbl_name
WHERE key_column IN (10,20,30);
 
SELECT * FROM tbl_name
WHERE key_part1= 10 AND key_part2 IN (10,20,30);
  • index

該聯接類型與ALL相同,除了只有索引樹被掃描。這通常比ALL快,因為索引文件通常比數據文件小。

當查詢只使用作為單索引一部分的列時,MySQL可以使用該聯接類型。

  • ALL

對於每個來自於先前的表的行組合,進行完整的表掃描。如果表是第一個沒標記const的表,這通常不好,並且通常在它情況下差。通常可以增加更多的索引而不要使用ALL,使得行能基於前面的表中的常數值或列值被檢索出。

5>> possible_keys

possible_keys列指出MySQL能使用哪個索引在該表中找到行。注意,該列完全獨立於EXPLAIN輸出所示的表的次序。這意味著在possible_keys中的某些鍵實際上不能按生成的表次序使用。如果該列是NULL,則沒有相關的索引。在這種情況下,可以通過檢查WHERE子句看是否它引用某些列或適合索引的列來提高你的查詢性能。如果是這樣,創造一個適當的索引並且再次用

6>> key

key列顯示MySQL實際決定使用的鍵(索引)。如果沒有選擇索引,鍵是NULL。要想強制MySQL使用或忽視possible_keys列中的索引,在查詢中使用FORCE INDEX、USE INDEX或者IGNORE INDEX

7>> key_len

key_len列顯示MySQL決定使用的鍵長度。如果鍵是NULL,則長度為NULL。注意通過key_len值我們可以確定MySQL將實際使用一個多部關鍵字的幾個部分。

MySQL Explain詳解,添加索引sql優化

8>> ref

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

9>> rows

rows列顯示MySQL認為它執行查詢時必須檢查的行數。

10>> Extra

該列包含MySQL解決查詢的詳細信息。下面解釋了該列可以顯示的不同的文本字符串:

  • Distinct

MySQL發現第1個匹配行後,停止為當前的行組合搜索更多的行。

  • Not exists

MySQL能夠對查詢進行LEFT JOIN優化,發現1個匹配LEFT JOIN標準的行後,不再為前面的的行組合在該表內檢查更多的行。

下面是一個可以這樣優化的查詢類型的例子:

SELECT * 從t1 LEFT JOIN t2 ON t1.id=t2.id
 WHERE t2.id IS NULL;

假定t2.id定義為NOT NULL。在這種情況下,MySQL使用t1.id的值掃描t1並查找t2中的行。如果MySQL在t2中發現一個匹配的行,它知道t2.id絕不會為NULL,並且不再掃描t2內有相同的id值的行。換句話說,對於t1的每個行,MySQL只需要在t2中查找一次,無論t2內實際有多少匹配的行。

  • range checked for each record (index map: #)

MySQL沒有發現好的可以使用的索引,但發現如果來自前面的表的列值已知,可能部分索引可以使用。對前面的表的每個行組合,MySQL檢查是否可以使用range或index_merge訪問方法來索取行。

  • Using filesort

MySQL需要額外的一次傳遞,以找出如何按排序順序檢索行。通過根據聯接類型瀏覽所有行併為所有匹配WHERE子句的行保存排序關鍵字和行的指針來完成排序。然後關鍵字被排序,並按排序順序檢索行。

  • Using index

從只使用索引樹中的信息而不需要進一步搜索讀取實際的行來檢索表中的列信息。當查詢只使用作為單一索引一部分的列時,可以使用該策略。

  • Using temporary

為了解決查詢,MySQL需要創建一個臨時表來容納結果。典型情況如查詢包含可以按不同情況列出列的GROUP BY和ORDER BY子句時。

  • Using where

WHERE子句用於限制哪一個行匹配下一個表或發送到客戶。除非你專門從表中索取或檢查所有行,如果Extra值不為Using where並且表聯接類型為ALL或index,查詢可能會有一些錯誤。

  • Using sort_union(...), Using union(...), Using intersect(...)

這些函數說明如何為index_merge聯接類型合併索引掃描。詳細信息參見7.2.6節,“索引合併優化”。

  • Using index for group-by

類似於訪問表的Using index方式,Using index for group-by表示MySQL發現了一個索引,可以用來查詢GROUP BY或DISTINCT查詢的所有列,而不要額外搜索硬盤訪問實際的表。並且,按最有效的方式使用索引,以便對於每個組,只讀取少量索引條目。

通過相乘EXPLAIN輸出的rows列的所有值,你能得到一個關於一個聯接如何的提示。這應該粗略地告訴你

MySQL必須檢查多少行以執行查詢。當你使用max_join_size變量限制查詢時,也用這個乘積來確定執行哪個多表SELECT語句。

例子說明:

添加索引優化器更高效率地執行語句

假設我們有兩個數據表a和b,每個有20行,包含的值從1到20。下面的查詢查找出兩個表中姓名相同的行

表a的結構

MySQL Explain詳解,添加索引sql優化

表a的數據:

MySQL Explain詳解,添加索引sql優化

a表

表b的表結構

MySQL Explain詳解,添加索引sql優化

表b的數據

MySQL Explain詳解,添加索引sql優化

下面我們查詢a b表姓名相同的行

代碼:

SELECT a.`NAME`, b.`NAME` FROM a, b WHERE a.`NAME` = b.`NAME`; 
MySQL Explain詳解,添加索引sql優化

兩個表都沒有索引的時候,EXPLAIN產生下面的結果:

EXPLAIN SELECT a.`NAME`, b.`NAME` FROM a, b WHERE a.`NAME` = b.`NAME`; 
MySQL Explain詳解,添加索引sql優化

類型列中的ALL表明要進行檢查所有數據行的全表掃描。可能鍵列中的NULL表明沒有找到用於提高查詢速度的備選索引(鍵、鍵長度和參考列都是NULL也是因為缺少合適的索引)。Using where表明使用WHERE子句中的信息來識別合格的數據行。 這段信息告訴我們,優化器沒有為提高執行查詢的效率找到任何有用的信息:

它將對a表進行全表掃描。

對於a中的每一行,它將執行b的全表掃描,使用WHERE子句中的信息識別出合格的行。

行數值顯示了優化器估計的每個階段查詢需要檢查的行數。a的估計值是18,因為b可以完成全表掃描。相似地,b的估計值也是20,但是這個值是對於a的每一行的。換句話說,優化器所估計的處理該查詢所需要檢查的數據行組合的數量是18×20,也就是360。這會造成很大的浪費 ,因為實際上只有20個組合符合WHERE子句的條件。

為了使這個查詢的效率更高,給其中一個聯結列添加索引 並重新執行EXPLAIN語句:

/*把b表name添加索引*/
 ALTER TABLE b ADD INDEX (NAME); 
MySQL Explain詳解,添加索引sql優化

然後回來查看

/*查詢兩張表沒有索引的情況下,EXPLAIN的值*/
EXPLAIN SELECT a.`NAME`, b.`NAME` FROM a, b WHERE a.`NAME` = b.`NAME`; 
MySQL Explain詳解,添加索引sql優化

我們可以看到性能提高了。a的輸出沒有改變(表明還是需要進行全表掃描),但是優化器處理b表的方式就有所不同了:

類型從ALL改變為ref,意味著可以使用參考值(來自a的值)來執行索引查找,定位b中合格的數據行。

參考值在參考(ref)字段中給出了:springboot.a.name。

數值從20降低到了1,顯示出優化器相信對於a中的每一行,它只需要檢查b中的1行(實際上,在a中只有一行與 b中數據行匹配。我們在後面會看到如何幫助優化器改善這個估計值)。數據行組合的全部估計值使18×1=18。它比前面的沒有索引的時候估 計出來的好多了。

對a進行索引有價值嗎?實際上,對於這個特定的聯結操作,掃描一張表是必要的,因此沒有必要對a建立索引。如果你想看到效果,可以索引a.name並再次運行EXPLAIN:

a表的name添加索引

MySQL Explain詳解,添加索引sql優化

然後回來查看

/*查詢兩張表沒有索引的情況下,EXPLAIN的值*/
EXPLAIN SELECT a.`NAME`, b.`NAME` FROM a, b WHERE a.`NAME` = b.`NAME`; 
MySQL Explain詳解,添加索引sql優化

上面的輸出與前面的EXPLAIN的輸出相似,但是添加索引對a表的輸出有一些改變。類型從NULL改成了index,附加(Extra)從空的改成了 Using index。這些改變表明,儘管對索引的值仍然需要執行全表掃描,但是優化器還是可以直接從索引文件中讀取值,根據不需要使用數據文件。在這種情況下優化器可以單獨使用索引中的信息而不用搜索數據行。

謝謝閱讀,自己關注哦


分享到:


相關文章: