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