MySQL——EXPLAIN和执行计划
EXPLAIN
MySQL?里提供一個解釋命令EXPLAIN。EXPLAIN的用法有兩種,分別是:
1、解釋表結構,等同于DESCRIBE,語法:?
EXPLAIN table_name;?
2、解釋查詢語句,獲得關于?MySQL?如何執行SELECT語句的信息,語法:?
EXPLAIN [EXTENDED|PARTITIONS] SELECT select_options;
這里主要討論一下EXPLAIN的第二種用法。
作用
在SELECT語句前加上關鍵詞EXPLAIN,MySQL?會解釋它將如何處理這句SELECT,比如如何聯接有關表和聯接的次序。
借助EXPLAIN,可以知道什么時候必須為表加入索引以得到一個使用索引來查找記錄的更快的SELECT,還可以知道?MySQL優化器?是否以一個最佳次序來聯接有關表(一般情況下,MySQL?會對查詢語句進行優化后再執行,如果想強制優化器讓一個SELECT語句按照表命名順序的聯接次序,語句前頭可以加上STRAIGHT_JOIN)。
EXPLAIN為SELECT語句中用到的每個表都返回一行信息。被用到的表,會按它們將在處理查詢過程中被?MySQL?讀入的順序列出。?
對于需要用到的表,MySQL?會用一遍掃描、多次聯接(single-sweep?&?multi-join)的方式解決所有聯接。這意味著?MySQL?從第一張表中讀一行,然后在第二張表中查找一個匹配行,然后在第三張表中查找……依此類推;在所有關聯的表中查找完后,MySQL會輸出獲得的記錄(行)中被選中(SELECTE)的列,并且回溯相關的表,查找更多的匹配行(直至沒有);然后再從第一張表讀入下一行,繼續在后面的表中找匹配行。
當使用EXTENDED選項時,EXPLAIN結果會多出一列filtered:filtered列中的值勢一個百分比,這個百分比值和rows列的值一起使用,可以估計與前一個表進行聯接的行的數目。?
執行完EXPLAIN EXTENDED后,再執行SHOW WARNINGS語句可以查看經過?MySQL優化器?執行優化規則后的SELECT語句是什么樣子的,而且還可能包括一些優化過程中的注解。
EXPLAIN的輸出格式
EXPLAIN輸出列:
| id | SELECT序列號。 |
| select_type | SELECT類型。 |
| table | 所引用表的名字。 |
| type | 聯接類型。 |
| possible_keys | 可以用到的索引。 |
| key | 實際使用的索引。 |
| key_len | 所用索引的長度(單位Byte)。 |
| ref | 表中配合索引檢索的列名。 |
| rows | MySQL認為執行查詢時必須檢查的行數。 |
| Extra | 關于MYSQL如何解析查詢的額外信息 。 |
輸出結果
id
id指明了MySQL對表的查詢順序。如果該行是其他行聯合UNION的結果,那么此處id的值時NULL;而table的值是<union M, N>。
select_type
select_type指明?MySQL?對表的查詢類型。可選的值有:
- SIMPLE:簡單的SELECT(不使用聯合UNION或子查詢SUBQUERY)。
- PRIMARY:最外層的SELECT。
- UNION:處于UNION中的第二個的(或者說后面的)SELECT語句。
- DEPENDENT UNION:處于UNION中第二個(或者說后面的)SELECT語句,依賴于外部查詢。
- UNION RESULT:UNION的結果。
- SUBQUERY:子查詢中的第一個SELECT。
- DEPENDENT SUBQUERY:子查詢中的第一個SELECT,取決于外面的查詢。
- DERIVED:派生表的SELECT(FROM子句的子查詢)。
- UNCACHEABLE SUBQUERY:結果不能被緩存,必須重新掃描外部查詢的每一行的子查詢。
- UNCACHEABLE UNION:屬?于UNCACHEABLE SUBQUERY的UNION中第二個(或者說后面的)SELECT語句。
table
table顯示該行所引用表的名字。如果該行引用的是一個聯合UNION,那么table值的形式是<union M, N>,指的是由行id為M和N的SELECT的聯合;如果該行引用的是一個派生表(例如,在FROM子句里做子查詢),那么table值的形式是<derived N>?,指的是由行id為N的SELECT的派生。
type(重要)
type顯示了?MySQL?對該行引用表的連接方式,與后面的key值有關聯。可選的值有(性能從高到低排列):
- system:檢索的表中僅有一行(=系統表),這是const連接類型的一個特例。
-
const:檢索的表中最多只有一行匹配的記錄,它在查詢一開始的時候就會被讀取出來。因為僅有一行,在余下的優化程序里該行記錄的字段值可以被當作是一個常數值。const表查詢起來非常快,因為它們只需要被讀取一次!將主鍵(PRIMARY KEY)或唯一索引(UNIQUE INDEX)與常數值比較的時候,會用到const連接,如:
- SELECT * FROM tbl_name WHERE primary_key = 1;
- //聯合主鍵
- SELECT * FROM tbl_name WHERE primary_key_part1 = 1 AND primary_key_part2 = 2;
-
eq_ref:檢索的表中會有一行記錄被讀取出來以和之前的表讀取出來的記錄進行組合。不同于system和const,eq_ref是最好的連接方式(system和const沒有用到JOIN語句)。當一個索引的所有部分(所關聯的列)被聯接(JOIN ON)使用并且這個索引是主鍵(PRIMARY KEY)或唯一索引(UNIQUE INDEX)的時候,會用到eq_ref連接,在下面的例子中,MySQL可以使用eq_ref連接來處理ref_table表:
- 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:表中所有符合檢索值的記錄都會被取出來和從上一個表中取出來的記錄進行組合。使用一個不是PRIMARY KEY或UNIQUE INDEX的索引或者該索引(如果這是個多列索引)的最左前綴來連接表的時候(換句話說,就是連接程序無法根據鍵值只取得一條記錄),就會用到ref連接。當根據鍵值只查詢到少數匹配的記錄時,這時ref也是一個不錯的連接類型。 ref只能用于檢索字段使用=或<=>操作符來比較的時候。在下面的例子中,MySQL?將使用 ref連接來處理ref_table表:
- 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;
-
fulltext:連接表時使用了fulltext全文本索引。
-
ref_or_null:ref_or_null類似于ref,不同的是?MySQL?會在檢索的時候額外地搜索包含NULL值的記錄。在下面的例子中,MySQL?會使用ref_or_null 類型來處理 ref_table表:
- SELECT * FROM ref_table WHERE key_column <=> expr OR key_column IS NULL;
-
index_merge:index_merge表示連接表時使用了索引合并優化方法。當type值為index_merge,key值會包含所有使用的索引,key_len值會包含所有使用的索引的長度。
-
unique_subquery:unique_subquery是一個索引查找函數,可以完全替換IN子查詢,效率更高。unique_subquery會代替ref,被用在如下的一些IN子查詢語句中:?
value IN (SELECT primary_key FROM single_table WHERE some_expr) -
index_subquery:類似unique_subquery,可以替換IN子查詢,但只適合使用非唯一索引的子查詢:?
value IN (SELECT key_column FROM single_table WHERE some_expr) -
range:在使用索引去檢索時,只有鍵值符合給定范圍的記錄才會被取出來。當type值為range,key值指明使用哪個索引,key_len值指明索引的長度,而ref值是NULL。將一個有索引的字段用操作符=、<>、>、>=、<、<=、IS 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:index連接類型跟ALL一樣,不同的是它只掃描索引樹。它一般會比 all快,因為索引文件通常都比數據文件小。MySQL?在查詢的字段屬于一個單列索引的情況下使用index連接類型。
-
ALL:將對該表做全部掃描,來與之前的表中取得的記錄進行組合。這時候,如果第一個表沒有被標識為const的話,整個執行效率就不大好了,而且通常情況下是非常糟糕的。一般來說,可以通過增加索引來避免ALL,使得能從表中更快的取得記錄。
possible_keys
possible_keys顯示了對于查詢條件,MySQL?可以使用哪些索引。
key
key顯示?MySQL實際決定使用的索引。如果沒有選擇索引,key值是NULL。要想強制?MySQL?使用或忽視possible_keys列中的索引,可以在SELECT語句中使用FORCE INDEX、USE INDEX或者IGNORE INDEX。
ref
ref顯示了哪些字段或者常數值被用來和key值中指明的索引配合從表中查詢記錄。
row
row顯示了?MySQL?認為在查詢中應該檢索這張表的記錄數。
Extra
Extra顯示了?MySQL?在查詢中的一些附加信息。可選的值有:
-
Distinct:MySQL?發現第1個匹配行后,停止為當前的行組合搜索更多的行。
-
Not exists:MySQL?在查詢時會對LEFT JOIN進行優化,找到一個匹配LEFT JOIN條件的記錄行后,不再為前面的的行組合在該表內檢查更多的行。下面是一個可以這樣優化的例子,MySQL使用t1.id的值掃描t1表并查找t2表中的行。如果?MySQL?在t2表中發現一個匹配的行,它知道t2.id絕不會為NULL,并且不再掃描t2內有相同的id值的行。換句話說,對于t1的每個行,MySQL?只需要在t2中查找一次,無論t2內實際有多少匹配的行:
- SELECT * FROM t1 LEFT JOIN t2 ON t1.id = t2.id WHERE t2.id IS NULL;
-
range checked for each record (index map: N):MySQL?沒有找到有效的索引來使用,但發現來自前面的表的字段值已知,可能部分索引可以使用。對前面的表的每個行組合,MySQL檢查是否可以使用range或index_merge訪問方法來獲取行。這并不很快,但比執行沒有索引的連接要快得多。
-
Using filesort:只有在使用ORDER BY的時候才可能會出現Using filesort。作為排序規則的字段如果沒有建立索引,那么就會出現文件排序。
-
Using index:作為排序規則的字段如果有建立索引,那么就會使用索引排序,即Using index。
-
Using temporary:為了解決多重查詢,MySQL需要創建一些臨時表來保存結果集。使用GROUP BY或ORDER BY語句時,經常出現需要臨時表的情況。
-
Using where:WHERE子句用于限制表中哪一行去匹配下一張表或者返回數據。只有在使用WHERE語句的時候才可能會出現Using where。作為限制規則的字段如果沒有建立索引,那么就會出現Using where,如果有建立索引,那么就會使用索引限制,即Using index。
-
Using index for group-by:類似于訪問表的Using index方式,Using index for group-by表示MySQL發現了一個索引,可以用來查詢GROUP BY或DISTINCT查詢的所有列,而不要額外搜索硬盤訪問實際的表。
額外
最左前綴:MySQL在建立索引的時候是以B-樹結構建立的,建立一個多列索引INDEX(a, b, c)是,會先建立a的按順序排列的索引,在a相同的情況下建立b的按順序排列的索引,最后在b相同的情況下建立c的按順序排的索引,如果列數更多也依此類推。a是這個索引的最左前綴,這個索引必須要配合a來使用,沒有a時這個索引不起作用。相當于建立了INDEX(a)、INDEX(a, b)和INDEX(a, b, c)。
注意
范例
下面是一個多表連接查詢是如何使用EXPLAIN提供的信息逐步優化的例子。
現有SELECT語句:
已知,被比較的字段的聲明如下:
| tt | ActualPC | CHAR(10) |
| tt | AssignedPC | CHAR(10) |
| tt | ClientID | CHAR(10) |
| et | EMPLOYID | CHAR(15) |
| do | CUSTNMBR | CHAR(15) |
所有表的索引如下:
| tt | ActualPC |
| tt | AssignedPC |
| tt | ClientID |
| et | EMPLOYID(PRIMARY KEY) |
| do | CUSTNMBR(PRIMARY KEY) |
還有,tt表中的ActualPC字段的值是不均勻分布的。
下面開始優化:
1、EXPLAIN查詢語句,獲得下列信息:
| et | ALL | PRIMARY | NULL | NULL | NULL | 74 | ? |
| do | ALL | PRIMARY | NULL | NULL | NULL | 2135 | ? |
| et_1 | ALL | PRIMARY | NULL | NULL | NULL | 74 | ? |
| tt | ALL | AssignedPC, ClientID, ActualPC | NULL | NULL | NULL | 3872 | Range checked for each record (index map: 0x23) |
可以看出每張表的連接類型type都是ALL,MySQL需要對所有表做一個笛卡爾乘,即將所有表的每一行都連接起來。這要消耗很多時間!對于這個一個實例,需要檢索74 * 2135 * 74 * 3872 = 45,268,558,720行記錄。如果表更大,花費的時間更多。
這里連接類型type都是ALL的原因是WHERE語句不起作用,例如tt.ActualPC始終不等于et.EMPLOYID,因為兩者的數據長度不同(數據類型是相同的)。
使用ALTER TABLE將ActualPC的長度從10個字符變為15個字符,改為VARCHAR(15)而不是CHAR(15),VARCHAR(15)可以與CHAR(15)匹配,同時可以節省空間(只占用原來10個長度):
2、再次執行EXPLAIN語句:
| tt | ALL | AssignedPC, ClientID, ActualPC | NULL | NULL | NULL | 3872 | Using where |
| do | ALL | PRIMARY | NULL | NULL | NULL | 2135 | Range checked for each record (index map: 0x1) |
| et_1 | ALL | PRIMARY | NULL | NULL | NULL | 74 | Range checked for each record (index map: 0x1) |
| et | eq_ref | PRIMARY | PRIMARY | 15 | tt.ActualPC | 1 | ? |
使用ALTER TABLE將AssignedPC和ClientID的長度從10個字符變為15個字符:
3、繼續執行EXPLAIN語句:
| et | ALL | PRIMARY | NULL | NULL | NULL | 74 | ? |
| tt | ref | AssignedPC, ClientID, ActualPC | ActualPC | 15 | et.EMPLOYID | 52 | Using where |
| et_1 | eq_ref | PRIMARY | PRIMARY | 15 | tt.AssignedPC | 1 | ? |
| do | eq_ref | PRIMARY | PRIMARY | 15 | tt.ClientID | 1 | ? |
這是的查詢語句的效率已經很不錯了。還有個問題,一般情況下,MySQL?認為表中字段的值是均勻分布的,但是tt表中的ActualPC字段的值是不均勻分布的。這時可以通過ANALYZE語句來統計整理表的信息和更新索引內容:
4、最后再執行一次EXPLAIN語句:
| tt | ALL | AssignedPC, ClientID, ActualPC | NULL | NULL | NULL | 3872 | Using where |
| et | eq_ref | PRIMARY | PRIMARY | 15 | tt.ActualPC | 1 | ? |
| et_1 | eq_ref | PRIMARY | PRIMARY | 15 | tt.AssignedPC | 1 | ? |
| do | eq_ref | PRIMARY | PRIMARY | 15 | tt.ClientID | 1 | ? |
現在的連接可以說是“完美”的了。
from:?http://breeze.leanote.com/post/mysql_command_explain
總結
以上是生活随笔為你收集整理的MySQL——EXPLAIN和执行计划的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: SQL逻辑查询语句执行顺序
- 下一篇: RPC框架几行代码就够了