7.2.1 EXPLAIN 語法(得到SELECT 的相關信息)
EXPLAIN tbl_name
或者:
EXPLAIN SELECT select_options
EXPLAIN 語句可以被當作 DESCRIBE 的同義詞來用,也可以用來獲取一個MySQL要執(zhí)行的 SELECT 語句的相關信息。
EXPLAIN tbl_name語法和DESCRIBE tbl_name或SHOW COLUMNS FROM tbl_name一樣。- 當在一個
SELECT語句前使用關鍵字EXPLAIN時,MYSQL會解釋了即將如何運行該SELECT語句,它顯示了表如何連接、連接的順序等信息。
本章節(jié)主要講述了第二種 EXPLAIN 用法。
在 EXPLAIN 的幫助下,您就知道什么時候該給表添加索引,以使用索引來查找記錄從而讓 SELECT 運行更快。
如果由于不恰當使用索引而引起一些問題的話,可以運行 ANALYZE TABLE 來更新該表的統(tǒng)計信息,例如鍵的基數(shù),它能幫您在優(yōu)化方面做出更好的選擇。詳情請看"14.5.2.1 ANALYZE TABLE Syntax"。
您還可以查看優(yōu)化程序是否以最佳的順序來連接數(shù)據(jù)表。為了讓優(yōu)化程序按照 SELECT 語句中的表名的順序做連接,可以在查詢的開始使用 SELECT STRAIGHT_JOIN 而不只是 SELECT。
EXPLAIN 返回了一行記錄,它包括了 SELECT 語句中用到的各個表的信息。這些表在結果中按照MySQL即將執(zhí)行的查詢中讀取的順序列出來。MySQL用一次掃描多次連接(single-sweep, multi-join) 的方法來解決連接。這意味著MySQL從第一個表中讀取一條記錄,然后在第二個表中查找到對應的記錄,然后在第三個表中查找,依次類推。當所有的表都掃描完了,它輸出選擇的字段并且回溯所有的表,直到找不到為止,因為有的表中可能有多條匹配的記錄下一條記錄將從該表讀取,再從下一個表開始繼續(xù)處理。
在MySQL version 4.1中,EXPLAIN 輸出的結果格式改變了,使得它更適合例如 UNION 語句、子查詢以及派生表的結構。更令人注意的是,它新增了2個字段: id 和 select_type。當你使用早于MySQL 4.1的版本就看不到這些字段了。
EXPLAIN 結果的每行記錄顯示了每個表的相關信息,每行記錄都包含以下幾個字段:
id
- 本次
SELECT的標識符。在查詢中每個SELECT都有一個順序的數(shù)值。 select_type
SELECT的類型,可能會有以下幾種:SIMPLE
- 簡單的
SELECT(沒有使用UNION或子查詢)
PRIMARY
- 最外層的
SELECT。
UNION
- 第二層,在
SELECT之后使用了UNION。
DEPENDENT UNION
UNION語句中的第二個SELECT,依賴于外部子查詢
SUBQUERY
- 子查詢中的第一個
SELECT
DEPENDENT SUBQUERY
- 子查詢中的第一個
SUBQUERY依賴于外部的子查詢
DERIVED
- 派生表
SELECT(FROM子句中的子查詢)
table
- 記錄查詢引用的表。
type
- 表連接類型。以下列出了各種不同類型的表連接,依次是從最好的到最差的:
system
- 表只有一行記錄(等于系統(tǒng)表)。這是
const表連接類型的一個特例。
const
- 表中最多只有一行匹配的記錄,它在查詢一開始的時候就會被讀取出來。由于只有一行記錄,在余下的優(yōu)化程序里該行記錄的字段值可以被當作是一個恒定值。
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 AND primary_key_part2=2;
eq_ref
- 從該表中會有一行記錄被讀取出來以和從前一個表中讀取出來的記錄做聯(lián)合。與
const類型不同的是,這是最好的連接類型。它用在索引所有部分都用于做連接并且這個索引是一個PRIMARY KEY或UNIQUE類型。eq_ref可以用于在進行"="做比較時檢索字段。比較的值可以是固定值或者是表達式,表達示中可以使用表里的字段,它們在讀表之前已經(jīng)準備好了。以下的幾個例子中,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
- 該表中所有符合檢索值的記錄都會被取出來和從上一個表中取出來的記錄作聯(lián)合。
ref用于連接程序使用鍵的最左前綴或者是該鍵不是PRIMARY KEY或UNIQUE索引(換句話說,就是連接程序無法根據(jù)鍵值只取得一條記錄)的情況。當根據(jù)鍵值只查詢到少數(shù)幾條匹配的記錄時,這就是一個不錯的連接類型。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;
ref_or_null
- 這種連接類型類似
ref,不同的是MySQL會在檢索的時候額外的搜索包含NULL值的記錄。這種連接類型的優(yōu)化是從MySQL 4.1.1開始的,它經(jīng)常用于子查詢。在以下的例子中,MySQL使用ref_or_null類型來處理 ref_table:SELECT * FROM ref_table WHERE key_column=expr OR key_column IS NULL;
詳情請看"7.2.6 How MySQL Optimizes
IS NULL"。 index_merge
- 這種連接類型意味著使用了
Index Merge優(yōu)化方法。這種情況下,key字段包括了所有使用的索引,key_len包括了使用的鍵的最長部分。詳情請看"7.2.5 How MySQL OptimizesORClauses"。
unique_subquery
- 這種類型用例如一下形式的
IN子查詢來替換ref:value IN (SELECT primary_key FROM single_table WHERE some_expr)
unique_subquery只是用來完全替換子查詢的索引查找函數(shù)效率更高了。 index_subquery
- 這種連接類型類似
unique_subquery。它用子查詢來代替IN,不過它用于在子查詢中沒有唯一索引的情況下,例如以下形式:value IN (SELECT key_column FROM single_table WHERE some_expr)
range
- 只有在給定范圍的記錄才會被取出來,利用索引來取得一條記錄。
key字段表示使用了哪個索引。key_len字段包括了使用的鍵的最長部分。這種類型時ref字段值是NULL。range用于將某個字段和一個定植用以下任何操作符比較時=,<>,>,>=,<,<=,IS NULL,<=>,BETWEEN, 或IN: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快點,因為索引文件通常比數(shù)據(jù)文件小。MySQL在查詢的字段知識單獨的索引的一部分的情況下使用這種連接類型。
ALL
- 將對該表做全部掃描以和從前一個表中取得的記錄作聯(lián)合。這時候如果第一個表沒有被標識為
const的話就不大好了,在其他情況下通常是非常糟糕的。正常地,可以通過增加索引使得能從表中更快的取得記錄以避免ALL。
possible_keys
possible_keys字段是指MySQL在搜索表記錄時可能使用哪個索引。注意,這個字段完全獨立于EXPLAIN顯示的表順序。這就意味著possible_keys里面所包含的索引可能在實際的使用中沒用到。如果這個字段的值是NULL,就表示沒有索引被用到。這種情況下,就可以檢查WHERE子句中哪些字段那些字段適合增加索引以提高查詢的性能。就這樣,創(chuàng)建一下索引,然后再用EXPLAIN檢查一下。詳細的查看章節(jié)"14.2.2ALTER TABLESyntax"。想看表都有什么索引,可以通過SHOW INDEX FROM tbl_name來看。-
key
key字段顯示了MySQL實際上要用的索引。當沒有任何索引被用到的時候,這個字段的值就是NULL。想要讓MySQL強行使用或者忽略在possible_keys字段中的索引列表,可以在查詢語句中使用關鍵字FORCE INDEX,USE INDEX, 或IGNORE INDEX。如果是MyISAM和BDB類型表,可以使用ANALYZE TABLE來幫助分析使用使用哪個索引更好。如果是MyISAM類型表,運行命令myisamchk --analyze也是一樣的效果。詳細的可以查看章節(jié)"14.5.2.1ANALYZE TABLESyntax"和"5.7.2 Table Maintenance and Crash Recovery"。key_len
key_len字段顯示了MySQL使用索引的長度。當key字段的值為NULL時,索引的長度就是NULL。注意,key_len的值可以告訴你在聯(lián)合索引中MySQL會真正使用了哪些索引。ref
ref字段顯示了哪些字段或者常量被用來和key配合從表中查詢記錄出來。rows
rows字段顯示了MySQL認為在查詢中應該檢索的記錄數(shù)。Extra
- 本字段顯示了查詢中MySQL的附加信息。以下是這個字段的幾個不同值的解釋:
Distinct
- MySQL當找到當前記錄的匹配聯(lián)合結果的第一條記錄之后,就不再搜索其他記錄了。
Not exists
- MySQL在查詢時做一個
LEFT JOIN優(yōu)化時,當它在當前表中找到了和前一條記錄符合LEFT JOIN條件后,就不再搜索更多的記錄了。下面是一個這種類型的查詢例子:SELECT * FROM t1 LEFT JOIN t2 ON t1.id=t2.id WHERE t2.id IS NULL;
假使
t2.id定義為NOT NULL。這種情況下,MySQL將會掃描表t1并且用t1.id的值在t2中查找記錄。當在t2中找到一條匹配的記錄時,這就意味著t2.id肯定不會都是NULL,就不會再在t2中查找相同id值的其他記錄了。也可以這么說,對于t1中的每個記錄,MySQL只需要在t2中做一次查找,而不管在t2中實際有多少匹配的記錄。 range checked for each record (index map: #)
- MySQL沒找到合適的可用的索引。取代的辦法是,對于前一個表的每一個行連接,它會做一個檢驗以決定該使用哪個索引(如果有的話),并且使用這個索引來從表里取得記錄。這個過程不會很快,但總比沒有任何索引時做表連接來得快。
Using filesort
- MySQL需要額外的做一遍從而以排好的順序取得記錄。排序程序根據(jù)連接的類型遍歷所有的記錄,并且將所有符合
WHERE條件的記錄的要排序的鍵和指向記錄的指針存儲起來。這些鍵已經(jīng)排完序了,對應的記錄也會按照排好的順序取出來。詳情請看"7.2.9 How MySQL OptimizesORDER BY"。 Using index
- 字段的信息直接從索引樹中的信息取得,而不再去掃描實際的記錄。這種策略用于查詢時的字段是一個獨立索引的一部分。
Using temporary
- MySQL需要創(chuàng)建臨時表存儲結果以完成查詢。這種情況通常發(fā)生在查詢時包含了
GROUP BY和ORDER BY子句,它以不同的方式列出了各個字段。 Using where
WHERE子句將用來限制哪些記錄匹配了下一個表或者發(fā)送給客戶端。除非你特別地想要取得或者檢查表種的所有記錄,否則的話當查詢的Extra字段值不是Using where并且表連接類型是ALL或index時可能表示有問題。
如果你想要讓查詢盡可能的快,那么就應該注意Extra字段的值為Using filesort和Using temporary的情況。
你可以通過 EXPLAIN 的結果中 rows 字段的值的乘積大概地知道本次連接表現(xiàn)如何。它可以粗略地告訴我們MySQL在查詢過程中會查詢多少條記錄。如果是使用系統(tǒng)變量 max_join_size 來取得查詢結果,這個乘積還可以用來確定會執(zhí)行哪些多表 SELECT 語句。詳情請看"7.5.2 Tuning Server Parameters"。
下面的例子展示了如何通過 EXPLAIN 提供的信息來較大程度地優(yōu)化多表聯(lián)合查詢的性能。
假設有下面的 SELECT 語句,正打算用 EXPLAIN 來檢測:
EXPLAIN SELECT tt.TicketNumber, tt.TimeIn, tt.ProjectReference, tt.EstimatedShipDate, tt.ActualShipDate, tt.ClientID, tt.ServiceCodes, tt.RepetitiveID, tt.CurrentProcess, tt.CurrentDPPerson, tt.RecordVolume, tt.DPPrinted, et.COUNTRY, et_1.COUNTRY, do.CUSTNAME FROM tt, et, et AS et_1, do WHERE tt.SubmitTime IS NULL AND tt.ActualPC = et.EMPLOYID AND tt.AssignedPC = et_1.EMPLOYID AND tt.ClientID = do.CUSTNMBR;
在這個例子中,先做以下假設:
- 要比較的字段定義如下:
Table Column Column Type ttActualPCCHAR(10)ttAssignedPCCHAR(10)ttClientIDCHAR(10)etEMPLOYIDCHAR(15)doCUSTNMBRCHAR(15) - 數(shù)據(jù)表的索引如下:
Table Index ttActualPCttAssignedPCttClientIDetEMPLOYID(primary key)doCUSTNMBR(primary key) tt.ActualPC的值是不均勻分布的。
在任何優(yōu)化措施未采取之前,經(jīng)過 EXPLAIN 分析的結果顯示如下:
table type possible_keys key key_len ref rows Extra 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, NULL NULL NULL 3872 ClientID, ActualPC range checked for each record (key map: 35)
由于字段 type 的對于每個表值都是 ALL,這個結果意味著MySQL對所有的表做一個迪卡爾積;這就是說,每條記錄的組合。這將需要花很長的時間,因為需要掃描每個表總記錄數(shù)乘積的總和。在這情況下,它的積是 74 * 2135 * 74 * 3872 = 45,268,558,720 條記錄。如果數(shù)據(jù)表更大的話,你可以想象一下需要多長的時間。
在這里有個問題是當字段定義一樣的時候,MySQL就可以在這些字段上更快的是用索引(對 ISAM 類型的表來說,除非字段定義完全一樣,否則不會使用索引)。在這個前提下,VARCHAR 和 CHAR是一樣的除非它們定義的長度不一致。由于 tt.ActualPC 定義為 CHAR(10),et.EMPLOYID 定義為 CHAR(15),二者長度不一致。
為了解決這個問題,需要用 ALTER TABLE 來加大 ActualPC 的長度從10到15個字符:
mysql> ALTER TABLE tt MODIFY ActualPC VARCHAR(15);
現(xiàn)在 tt.ActualPC 和 et.EMPLOYID 都是 VARCHAR(15)
了。再來執(zhí)行一次 EXPLAIN 語句看看結果:
table type possible_keys key key_len ref rows Extra tt ALL AssignedPC, NULL NULL NULL 3872 Using ClientID, where ActualPC do ALL PRIMARY NULL NULL NULL 2135 range checked for each record (key map: 1) et_1 ALL PRIMARY NULL NULL NULL 74 range checked for each record (key map: 1) et eq_ref PRIMARY PRIMARY 15 tt.ActualPC 1
這還不夠,它還可以做的更好:現(xiàn)在 rows 值乘積已經(jīng)少了74倍。這次查詢需要用2秒鐘。
第二個改變是消除在比較 tt.AssignedPC = et_1.EMPLOYID 和 tt.ClientID = do.CUSTNMBR 中字段的長度不一致問題:
mysql> ALTER TABLE tt MODIFY AssignedPC VARCHAR(15), -> MODIFY ClientID VARCHAR(15);
現(xiàn)在 EXPLAIN 的結果如下:
table type possible_keys key key_len ref rows Extra et ALL PRIMARY NULL NULL NULL 74 tt ref AssignedPC, ActualPC 15 et.EMPLOYID 52 Using ClientID, where ActualPC et_1 eq_ref PRIMARY PRIMARY 15 tt.AssignedPC 1 do eq_ref PRIMARY PRIMARY 15 tt.ClientID 1
這看起來已經(jīng)是能做的最好的結果了。
遺留下來的問題是,MySQL默認地認為字段tt.ActualPC 的值是均勻分布的,然而表 tt 并非如此。幸好,我們可以很方便的讓MySQL分析索引的分布:
mysql>ANALYZE TABLE tt;
到此為止,表連接已經(jīng)優(yōu)化的很完美了,EXPLAIN 的結果如下:
table type possible_keys key key_len ref rows Extra tt ALL AssignedPC NULL NULL NULL 3872 Using ClientID, where ActualPC 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
請注意,EXPLAIN 結果中的 rows 字段的值也是MySQL的連接優(yōu)化程序大致猜測的,請檢查這個值跟真實值是否基本一致。如果不是,可以通過在 SELECT 語句中使用 STRAIGHT_JOIN 來取得更好的性能,同時可以試著在 FROM
分句中用不同的次序列出各個表。



