7.2.1. EXPLAIN語法(獲取SELECT相關信息)
EXPLAINtbl_name
或:
EXPLAIN [EXTENDED] SELECTselect_options
EXPLAIN語句可以用作DESCRIBE的一個同義詞,或獲得關于MySQL如何執行SELECT語句的信息:
·EXPLAINtbl_name是DESCRIBEtbl_name或SHOW COLUMNS FROMtbl_name的一個同義詞。
·如果在SELECT語句前放上關鍵詞EXPLAIN,MySQL將解釋它如何處理SELECT,提供有關表如何聯接和聯接的次序。
該節解釋EXPLAIN的第2個用法。
借助于EXPLAIN,可以知道什么時候必須為表加入索引以得到一個使用索引來尋找記錄的更快的SELECT。
如果由于使用不正確的索引出現了問題,應運行ANALYZE
TABLE更新表的統計(例如關鍵字集的勢),這樣會影響優化器進行的選擇。參見13.5.2.1節,“ANALYZE TABLE語法”。
還可以知道優化器是否以一個最佳次序聯接表。為了強制優化器讓一個SELECT語句按照表命名順序的聯接次序,語句應以STRAIGHT_JOIN而不只是SELECT開頭。
EXPLAIN為用于SELECT語句中的每個表返回一行信息。表以它們在處理查詢過程中將被MySQL讀入的順序被列出。MySQL用一遍掃描多次聯接(single-sweep?multi-join)的方式解決所有聯接。這意味著MySQL從第一個表中讀一行,然后找到在第二個表中的一個匹配行,然后在第3個表中等等。當所有的表處理完后,它輸出選中的列并且返回表清單直到找到一個有更多的匹配行的表。從該表讀入下一行并繼續處理下一個表。
當使用EXTENDED關鍵字時,EXPLAIN產生附加信息,可以用SHOW WARNINGS瀏覽。該信息顯示優化器限定SELECT語句中的表和列名,重寫并且執行優化規則后SELECT語句是什么樣子,并且還可能包括優化過程的其它注解。
EXPLAIN的每個輸出行提供一個表的相關信息,并且每個行包括下面的列:
·id
SELECT識別符。這是SELECT的查詢序列號。
·select_type
SELECT類型,可以為以下任何一種:
oSIMPLE
簡單SELECT(不使用UNION或子查詢)
oPRIMARY
最外面的SELECT
oUNION
UNION中的第二個或后面的SELECT語句
oDEPENDENT UNION
UNION中的第二個或后面的SELECT語句,取決于外面的查詢
oUNION RESULT
UNION的結果。
oSUBQUERY
子查詢中的第一個SELECT
oDEPENDENT SUBQUERY
子查詢中的第一個SELECT,取決于外面的查詢
oDERIVED
導出表的SELECT(FROM子句的子查詢)
·table
輸出的行所引用的表。
·type
聯接類型。下面給出各種聯接類型,按照從最佳類型到最壞類型進行排序:
osystem
表僅有一行(=系統表)。這是const聯接類型的一個特例。
oconst
表最多有一個匹配行,它將在查詢開始時被讀取。因為僅有一行,在這行的列值可被優化器剩余部分認為是常數。const表很快,因為它們只讀取一次!
const用于用常數值比較PRIMARY
KEY或UNIQUE索引的所有部分時。在下面的查詢中,tbl_name可以用于const表:
SELECT * fromtbl_nameWHEREprimary_key=1;
SELECT * fromtbl_name
WHEREprimary_key_part1=1和primary_key_part2=2;
oeq_ref
對于每個來自于前面的表的行組合,從該表中讀取一行。這可能是最好的聯接類型,除了const類型。它用在一個索引的所有部分被聯接使用并且索引是UNIQUE或PRIMARY
KEY。
eq_ref可以用于使用=操作符比較的帶索引的列。比較值可以為常量或一個使用在該表前面所讀取的表的列的表達式。
在下面的例子中,MySQL可以使用eq_ref聯接來處理ref_tables:
SELECT * FROMref_table,other_table
WHEREref_table.key_column=other_table.column;
SELECT * FROMref_table,other_table
WHEREref_table.key_column_part1=other_table.column
ANDref_table.key_column_part2=1;
oref
對于每個來自于前面的表的行組合,所有有匹配索引值的行將從這張表中讀取。如果聯接只使用鍵的最左邊的前綴,或如果鍵不是UNIQUE或PRIMARY
KEY(換句話說,如果聯接不能基于關鍵字選擇單個行的話),則使用ref。如果使用的鍵僅僅匹配少量行,該聯接類型是不錯的。
ref可以用于使用=或<=>操作符的帶索引的列。
在下面的例子中,MySQL可以使用ref聯接來處理ref_tables:
SELECT * FROMref_tableWHEREkey_column=expr;
SELECT * FROMref_table,other_table
WHEREref_table.key_column=other_table.column;
SELECT * FROMref_table,other_table
WHEREref_table.key_column_part1=other_table.column
ANDref_table.key_column_part2=1;
oref_or_null
該聯接類型如同ref,但是添加了MySQL可以專門搜索包含NULL值的行。在解決子查詢中經常使用該聯接類型的優化。
在下面的例子中,MySQL可以使用ref_or_null聯接來處理ref_tables:
SELECT * FROMref_table
WHEREkey_column=exprORkey_columnIS NULL;
oindex_merge
該聯接類型表示使用了索引合并優化方法。在這種情況下,key列包含了使用的索引的清單,key_len包含了使用的索引的最長的關鍵元素。詳細信息參見7.2.6節,“索引合并優化”。
ounique_subquery
該類型替換了下面形式的IN子查詢的ref:
valueIN (SELECTprimary_keyFROMsingle_tableWHEREsome_expr)
unique_subquery是一個索引查找函數,可以完全替換子查詢,效率更高。
oindex_subquery
該聯接類型類似于unique_subquery。可以替換IN子查詢,但只適合下列形式的子查詢中的非唯一索引:
valueIN (SELECTkey_columnFROMsingle_tableWHEREsome_expr)
orange
只檢索給定范圍的行,使用一個索引來選擇行。key列顯示使用了哪個索引。key_len包含所使用索引的最長關鍵元素。在該類型中ref列為NULL。
當使用=、<>、>、>=、<、<=、IS
NULL、<=>、BETWEEN或者IN操作符,用常量比較關鍵字列時,可以使用range:
SELECT * FROMtbl_name
WHEREkey_column= 10;
SELECT * FROMtbl_name
WHEREkey_columnBETWEEN 10 and 20;
SELECT * FROMtbl_name
WHEREkey_columnIN (10,20,30);
SELECT * FROMtbl_name
WHEREkey_part1= 10 ANDkey_part2IN (10,20,30);
oindex
該聯接類型與ALL相同,除了只有索引樹被掃描。這通常比ALL快,因為索引文件通常比數據文件小。
當查詢只使用作為單索引一部分的列時,MySQL可以使用該聯接類型。
oALL
對于每個來自于先前的表的行組合,進行完整的表掃描。如果表是第一個沒標記const的表,這通常不好,并且通常在它情況下很差。通常可以增加更多的索引而不要使用ALL,使得行能基于前面的表中的常數值或列值被檢索出。
·possible_keys
possible_keys列指出MySQL能使用哪個索引在該表中找到行。注意,該列完全獨立于EXPLAIN輸出所示的表的次序。這意味著在possible_keys中的某些鍵實際上不能按生成的表次序使用。
如果該列是NULL,則沒有相關的索引。在這種情況下,可以通過檢查WHERE子句看是否它引用某些列或適合索引的列來提高你的查詢性能。如果是這樣,創造一個適當的索引并且再次用EXPLAIN檢查查詢。參見13.1.2節,“ALTER TABLE語法”。
為了看清一張表有什么索引,使用SHOW INDEX FROMtbl_name。
·key
key列顯示MySQL實際決定使用的鍵(索引)。如果沒有選擇索引,鍵是NULL。要想強制MySQL使用或忽視possible_keys列中的索引,在查詢中使用FORCE
INDEX、USE INDEX或者IGNORE INDEX。參見13.2.7節,“SELECT語法”。
對于MyISAM和BDB表,運行ANALYZE
TABLE可以幫助優化器選擇更好的索引。對于MyISAM表,可以使用myisamchk
--analyze。參見13.5.2.1節,“ANALYZE TABLE語法”和5.9.4節,“表維護和崩潰恢復”。
·key_len
key_len列顯示MySQL決定使用的鍵長度。如果鍵是NULL,則長度為NULL。注意通過key_len值我們可以確定MySQL將實際使用一個多部關鍵字的幾個部分。
·ref
ref列顯示使用哪個列或常數與key一起從表中選擇行。
·rows
rows列顯示MySQL認為它執行查詢時必須檢查的行數。
·Extra
該列包含MySQL解決查詢的詳細信息。下面解釋了該列可以顯示的不同的文本字符串:
oDistinct
MySQL發現第1個匹配行后,停止為當前的行組合搜索更多的行。
oNot 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內實際有多少匹配的行。
orange checked for each record
(index map:#)
MySQL沒有發現好的可以使用的索引,但發現如果來自前面的表的列值已知,可能部分索引可以使用。對前面的表的每個行組合,MySQL檢查是否可以使用range或index_merge訪問方法來索取行。關于適用性標準的描述參見7.2.5節,“范圍優化”和7.2.6節,“索引合并優化”,不同的是前面表的所有列值已知并且認為是常量。
這并不很快,但比執行沒有索引的聯接要快得多。
oUsing filesort
MySQL需要額外的一次傳遞,以找出如何按排序順序檢索行。通過根據聯接類型瀏覽所有行并為所有匹配WHERE子句的行保存排序關鍵字和行的指針來完成排序。然后關鍵字被排序,并按排序順序檢索行。參見7.2.12節,“MySQL如何優化ORDER BY”。
oUsing index
從只使用索引樹中的信息而不需要進一步搜索讀取實際的行來檢索表中的列信息。當查詢只使用作為單一索引一部分的列時,可以使用該策略。
oUsing temporary
為了解決查詢,MySQL需要創建一個臨時表來容納結果。典型情況如查詢包含可以按不同情況列出列的GROUP
BY和ORDER BY子句時。
oUsing where
WHERE子句用于限制哪一個行匹配下一個表或發送到客戶。除非你專門從表中索取或檢查所有行,如果Extra值不為Using
where并且表聯接類型為ALL或index,查詢可能會有一些錯誤。
如果想要使查詢盡可能快,應找出Using filesort和Using
temporary的Extra值。
oUsing sort_union(...),Using union(...),Using
intersect(...)
這些函數說明如何為index_merge聯接類型合并索引掃描。詳細信息參見7.2.6節,“索引合并優化”。
oUsing index for group-by
類似于訪問表的Using index方式,Using index for
group-by表示MySQL發現了一個索引,可以用來查詢GROUP
BY或DISTINCT查詢的所有列,而不要額外搜索硬盤訪問實際的表。并且,按最有效的方式使用索引,以便對于每個組,只讀取少量索引條目。詳情參見7.2.13節,“MySQL如何優化GROUP BY”。
通過相乘EXPLAIN輸出的rows列的所有值,你能得到一個關于一個聯接如何的提示。這應該粗略地告訴你MySQL必須檢查多少行以執行查詢。當你使用max_join_size變量限制查詢時,也用這個乘積來確定執行哪個多表SELECT語句。參見7.5.2節,“調節服務器參數”。
下列例子顯示出一個多表JOIN如何能使用EXPLAIN提供的信息逐步被優化。
假定你有下面所示的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;
對于這個例子,假定:
·被比較的列聲明如下:
表
列
列類型
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(主鍵)
do
CUSTNMBR(主鍵)
·tt.ActualPC值不是均勻分布的。
開始,在進行優化前,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正在對所有表產生一個笛卡爾乘積;即每一個行的組合!這將花相當長的時間,因為必須檢查每張表的行數的乘積!對于一個實例,這是74
* 2135 * 74 * 3872 = 45,268,558,720行。如果表更大,你只能想象它將花多長時間……
這里的一個問題是MySQL能更高效地在聲明具有相同類型和尺寸的列上使用索引。在本文中,VARCHAR和CHAR是相同的,除非它們聲明為不同的長度。因為tt.ActualPC被聲明為CHAR(10)并且et.EMPLOYID被聲明為CHAR(15),長度不匹配。
為了修正在列長度上的不同,使用ALTER
TABLE將ActualPC的長度從10個字符變為15個字符:
mysql>ALTER TABLE tt MODIFY ActualPC VARCHAR(15);
現在tt.ActualPC和et.EMPLOYID都是VARCHAR(15),再執行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
這不是完美的,但是好一些了:rows值的乘積少了一個因子74。這個版本在幾秒內執行完。
第2種方法能消除tt.AssignedPC =
et_1.EMPLOYID和tt.ClientID = do.CUSTNMBR比較的列的長度失配問題:
mysql>ALTER TABLE tt MODIFY AssignedPC VARCHAR(15),
->MODIFY ClientID?? VARCHAR(15);
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
這幾乎很好了。
剩下的問題是,默認情況,MySQL假設在tt.ActualPC列的值是均勻分布的,并且對tt表不是這樣。幸好,很容易告訴MySQL來分析關鍵字分布:
mysql>ANALYZE TABLE tt;
現在聯接是“完美”的了,而且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聯接優化器的“教育猜測”。你應該檢查數字是否接近事實。如果不是,可以通過在SELECT語句里面使用STRAIGHT_JOIN并且試著在FROM子句以不同的次序列出表,可能得到更好的性能。