詳解 MySQL 中的 explain

發表於2016-06-29

在 explain的幫助下,您就知道什麼時候該給表新增索引,以使用索引來查詢記錄從而讓select 執行更快。如果由於不恰當使用索引而引起一些問題的話,可以執行 analyze table來更新該表的統計資訊,例如鍵的基數,它能幫您在優化方面做出更好的選擇。

explain 返回了一行記錄,它包括了 select語句中用到的各個表的資訊。這些表在結果中按照mysql即將執行的查詢中讀取的順序列出來。mysql用一次掃描多次連線(single- sweep,multi-join)的方法來解決連線。這意味著mysql從第一個表中讀取一條記錄,然後在第二個表中查詢到對應的記錄,然後在第三個表 中查詢,依次類推。當所有的表都掃描完了,它輸出選擇的欄位並且回溯所有的表,直到找不到為止,因為有的表中可能有多條匹配的記錄下一條記錄將從該表讀 取,再從下一個表開始繼續處理。

在mysql version 4.1中,explain輸出的結果格式改變了,使得它更適合例如 union語句、子查詢以及派生表的結構。更令人注意的是,它新增了2個欄位: id和 select_type。當你使用早於mysql4.1的版本就看不到這些欄位了。

explain結果的每行記錄顯示了每個表的相關資訊,每行記錄都包含以下幾個欄位:

id
本次 select 的識別符號。在查詢中每個 select都有一個順序的數值。
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:表只有一行記錄(等於系統表)。這是 const表連線型別的一個特例。

const:表中最多隻有一行匹配的記錄,它在查詢一開始的時候就會被讀取出來。由於只有一行記錄,在餘下的優化程式裡該行記錄的欄位值可以被當作是一個 恆定值。const表查詢起來非常快,因為只要讀取一次!const 用於在和 primary key 或unique 索引中有固定值比較的情形。下面的幾個查詢中,tbl_name 就是 c表了:

eq_ref:從該表中會有一行記錄被讀取出來以和從前一個表中讀取出來的記錄做聯合。與const型別不同的是,這是最好的連線型別。它用在索引所有部 分都用於做連線並且這個索引是一個primary key 或 unique 型別。

eq_ref可以用於在進行”=”做比較時檢索欄位。比較的值可以是固定值或者是表示式,表達示中可以使用表裡的欄位,它們在讀表之前已經準備好 了。以下的幾個例子中,mysql使用了eq_ref 連線來處理 ref_table:

ref: 該表中所有符合檢索值的記錄都會被取出來和從上一個表中取出來的記錄作聯合。ref用於連線程式使用鍵的最左字首或者是該鍵不是 primary key 或 unique索引(換句話說,就是連線程式無法根據鍵值只取得一條記錄)的情況。

當根據鍵值只查詢到少數幾條匹配的記錄時,這就是一個不錯的連線型別。 ref還可以用於檢索欄位使用 =操作符來比較的時候。以下的幾個例子中,mysql將使用 ref 來處理ref_table:

ref_or_null: 這種連線型別類似 ref,不同的是mysql會在檢索的時候額外的搜尋包含null 值的記錄。這種連線型別的優化是從mysql4.1.1開始的,它經常用於子查詢。在以下的例子中,mysql使用ref_or_null 型別來處理 ref_table:

unique_subquery: 這種型別用例如一下形式的 in 子查詢來替換 ref:

unique_subquery: 只是用來完全替換子查詢的索引查詢函式效率更高了。

index_subquery: 這種連線型別類似 unique_subquery。它用子查詢來代替in,不過它用於在子查詢中沒有唯一索引的情況下,例如以下形式:

range: 只有在給定範圍的記錄才會被取出來,利用索引來取得一條記錄。key欄位表示使用了哪個索引。key_len欄位包括了使用的鍵的最長部分。這種型別時 ref 欄位值是 null。range用於將某個欄位和一個定植用以下任何操作符比較時 =, <>, >,>=, <, <=, is null, <=>, between, 或 in:

index: 連線型別跟 all 一樣,不同的是它只掃描索引樹。它通常會比 all快點,因為索引檔案通常比資料檔案小。mysql在查詢的欄位知識單獨的索引的一部分的情況下使用這種連線型別。

all: 將對該表做全部掃描以和從前一個表中取得的記錄作聯合。這時候如果第一個表沒有被標識為const的話就不大好了,在其他情況下通常是非常糟糕的。正常地,可以通過增加索引使得能從表中更快的取得記錄以避免all。

possible_keys
possible_keys欄位是指 mysql在搜尋表記錄時可能使用哪個索引。注意,這個欄位完全獨立於explain 顯示的表順序。這就意味著 possible_keys裡面所包含的索引可能在實際的使用中沒用到。如果這個欄位的值是null,就表示沒有索引被用到。

這種情況下,就可以檢查 where子句中哪些欄位那些欄位適合增加索引以提高查詢的效能。就這樣,建立一下索引,然後再用explain 檢查一下。詳細的檢視章節”14.2.2 alter tablesyntax”。想看錶都有什麼索引,可以通過 show index from tbl_name來看。

key
key欄位顯示了mysql實際上要用的索引。當沒有任何索引被用到的時候,這個欄位的值就是null。想要讓mysql強行使用或者忽略在 possible_keys欄位中的索引列表,可以在查詢語句中使用關鍵字force index, use index,或 ignore index。

如果是 myisam 和 bdb 型別表,可以使用 analyzetable 來幫助分析使用使用哪個索引更好。如果是 myisam型別表,執行命令 myisamchk –analyze也是一樣的效果。詳細的可以檢視章節”14.5.2.1 analyze tablesyntax”和”5.7.2 table maintenance and crash recovery”。

key_len
key_len 欄位顯示了mysql使用索引的長度。當 key 欄位的值為 null時,索引的長度就是 null。注意,key_len的值可以告訴你在聯合索引中mysql會真正使用了哪些索引。

ref
ref 欄位顯示了哪些欄位或者常量被用來和 key配合從表中查詢記錄出來。

rows
rows 欄位顯示了mysql認為在查詢中應該檢索的記錄數。

extra
本欄位顯示了查詢中mysql的附加資訊。以下是這個欄位的幾個不同值的解釋:

distinct:mysql當找到當前記錄的匹配聯合結果的第一條記錄之後,就不再搜尋其他記錄了。

not exists:mysql在查詢時做一個 left join優化時,當它在當前表中找到了和前一條記錄符合 left join條件後,就不再搜尋更多的記錄了。下面是一個這種型別的查詢例子:

假使 t2.id 定義為 not null。這種情況下,mysql將會掃描表 t1並且用 t1.id 的值在 t2 中查詢記錄。當在 t2中找到一條匹配的記錄時,這就意味著 t2.id 肯定不會都是null,就不會再在 t2 中查詢相同 id值的其他記錄了。也可以這麼說,對於 t1 中的每個記錄,mysql只需要在t2 中做一次查詢,而不管在 t2 中實際有多少匹配的記錄。

mysql沒找到合適的可用的索引。取代的辦法是,對於前一個表的每一個行連線,它會做一個檢驗以決定該使用哪個索引(如果有的話),並且使用這個索引來從表裡取得記錄。這個過程不會很快,但總比沒有任何索引時做表連線來得快。

using filesort: mysql需要額外的做一遍從而以排好的順序取得記錄。排序程式根據連線的型別遍歷所有的記錄,並且將所有符合 where條件的記錄的要排序的鍵和指向記錄的指標儲存起來。這些鍵已經排完序了,對應的記錄也會按照排好的順序取出來。詳情請看”7.2.9how mysql optimizes order by”。

using index
欄位的資訊直接從索引樹中的資訊取得,而不再去掃描實際的記錄。這種策略用於查詢時的欄位是一個獨立索引的一部分。

using temporary: mysql需要建立臨時表儲存結果以完成查詢。這種情況通常發生在查詢時包含了groupby 和 order by 子句,它以不同的方式列出了各個欄位。

using where

where子句將用來限制哪些記錄匹配了下一個表或者傳送給客戶端。除非你特別地想要取得或者檢查表種的所有記錄,否則的話當查詢的extra 欄位值不是 using where 並且表連線型別是 all 或 index時可能表示有問題。

如果你想要讓查詢儘可能的快,那麼就應該注意 extra 欄位的值為usingfilesort 和 using temporary 的情況。

你可以通過 explain 的結果中 rows欄位的值的乘積大概地知道本次連線表現如何。它可以粗略地告訴我們mysql在查詢過程中會查詢多少條記錄。如果是使用系統變數 max_join_size 來取得查詢結果,這個乘積還可以用來確定會執行哪些多表select 語句。

下面的例子展示瞭如何通過 explain提供的資訊來較大程度地優化多表聯合查詢的效能。
假設有下面的 select 語句,正打算用 explain 來檢測:

在這個例子中,先做以下假設:

要比較的欄位定義如下:
table  column  columntype
tt  actualpc char(10)
tt  assignedpc char(10)
tt  clientid char(10)
et  employid char(15)
do  custnmbr char(15)

資料表的索引如下:
table  index
tt  actualpc
tt  assignedpc
tt  clientid
et  employid (primary key)
do  custnmbr (primary key)

tt.actualpc 的值是不均勻分佈的。

在任何優化措施未採取之前,經過 explain分析的結果顯示如下:

由於欄位 type 的對於每個表值都是all,這個結果意味著mysql對所有的表做一個迪卡爾積;這就是說,每條記錄的組合。這將需要花很長的時間,因為需要掃描每個表總 記錄數乘積的總和。在這情況下,它的積是74 * 2135 * 74 * 3872 = 45,268,558,720條記錄。如果資料表更大的話,你可以想象一下需要多長的時間。

在這裡有個問題是當欄位定義一樣的時候,mysql就可以在這些欄位上更快的是用索引(對isam型別的表來說,除非欄位定義完全一樣,否則不會使用索 引)。在這個前提下,varchar和 char是一樣的除非它們定義的長度不一致。由於 tt.actualpc 定義為char(10),et.employid 定義為 char(15),二者長度不一致。

為了解決這個問題,需要用 alter table 來加大 actualpc的長度從10到15個字元:

現在 tt.actualpc 和 et.employid 都是 varchar(15)
了。再來執行一次 explain 語句看看結果:

這還不夠,它還可以做的更好:現在 rows值乘積已經少了74倍。這次查詢需要用2秒鐘。
第二個改變是消除在比較 tt.assignedpc = et_1.employid 和 tt.clientid= do.custnmbr 中欄位的長度不一致問題:

現在 explain 的結果如下:

這看起來已經是能做的最好的結果了。
遺留下來的問題是,mysql預設地認為欄位 tt.actualpc的值是均勻分佈的,然而表 tt並非如此。幸好,我們可以很方便的讓mysql分析索引的分佈:

到此為止,表連線已經優化的很完美了,explain 的結果如下:

請注意,explain 結果中的 rows欄位的值也是mysql的連線優化程式大致猜測的,請檢查這個值跟真實值是否基本一致。如果不是,可以通過在select 語句中使用 straight_join 來取得更好的效能,同時可以試著在from分句中用不同的次序列出各個表。

相關文章