explain 返回了一行記錄,它包括了 select語句中用到的各個表的信息。這些表在結果中按照mysql即將執行的查詢中讀取的順序列出來。mysql用一次掃描屢次鏈接(single- sweep,multi-join)的方法來解決鏈接。這意味着mysql從第一個表中讀取一條記錄,而後在第二個表中查找到對應的記錄,而後在第三個表 中查找,依次類推。當全部的表都掃描完了,它輸出選擇的字段而且回溯全部的表,直到找不到爲止,由於有的表中可能有多條匹配的記錄下一條記錄將從該表讀 取,再從下一個表開始繼續處理。mysql
在mysql version 4.1中,explain輸出的結果格式改變了,使得它更適合例如 union語句、子查詢以及派生表的結構。更使人注意的是,它新增了2個字段: id和 select_type。當你使用早於mysql4.1的版本就看不到這些字段了。sql
explain結果的每行記錄顯示了每一個表的相關信息,每行記錄都包含如下幾個字段:函數
id
本次 select 的標識符。在查詢中每一個 select都有一個順序的數值。
select_type
select 的類型,可能會有如下幾種:
simple: 簡單的 select (沒有使用 union或子查詢)性能
primary: 最外層的 select。優化
union: 第二層,在select 以後使用了 union。spa
dependent union: union 語句中的第二個select,依賴於外部子查詢指針
subquery: 子查詢中的第一個 selectcode
dependent subquery: 子查詢中的第一個 subquery依賴於外部的子查詢排序
derived: 派生表 select(from子句中的子查詢)索引
table
記錄查詢引用的表。
type
錶鏈接類型。如下列出了各類不一樣類型的錶鏈接,依次是從最好的到最差的:
system:表只有一行記錄(等於系統表)。這是 const錶鏈接類型的一個特例。
const:表中最多隻有一行匹配的記錄,它在查詢一開始的時候就會被讀取出來。因爲只有一行記錄,在餘下的優化程序裏該行記錄的字段值能夠被看成是一個 恆定值。const表查詢起來很是快,由於只要讀取一次!const 用於在和 primary key 或unique 索引中有固定值比較的情形。下面的幾個查詢中,tbl_name 就是 c表了:
MySQL
1 2 |
select * from tbl_name where primary_key=1; select * from tbl_namewhere primary_key_part1=1 and primary_key_part2=2; |
eq_ref:從該表中會有一行記錄被讀取出來以和從前一個表中讀取出來的記錄作聯合。與const類型不一樣的是,這是最好的鏈接類型。它用在索引全部部 分都用於作鏈接而且這個索引是一個primary key 或 unique 類型。
eq_ref能夠用於在進行」=」作比較時檢索字段。比較的值能夠是固定值或者是表達式,表達示中可使用表裏的字段,它們在讀表以前已經準備好 了。如下的幾個例子中,mysql使用了eq_ref 鏈接來處理 ref_table:
MySQL
1 2 |
select * from ref_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; |
ref: 該表中全部符合檢索值的記錄都會被取出來和從上一個表中取出來的記錄做聯合。ref用於鏈接程序使用鍵的最左前綴或者是該鍵不是 primary key 或 unique索引(換句話說,就是鏈接程序沒法根據鍵值只取得一條記錄)的狀況。
當根據鍵值只查詢到少數幾條匹配的記錄時,這就是一個不錯的鏈接類型。 ref還能夠用於檢索字段使用 =操做符來比較的時候。如下的幾個例子中,mysql將使用 ref 來處理ref_table:
MySQL
1 2 3 |
select * from ref_table where key_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; |
ref_or_null: 這種鏈接類型相似 ref,不一樣的是mysql會在檢索的時候額外的搜索包含null 值的記錄。這種鏈接類型的優化是從mysql4.1.1開始的,它常常用於子查詢。在如下的例子中,mysql使用ref_or_null 類型來處理 ref_table:
MySQL
1 |
select * from ref_table where key_column=expr or key_column is null; |
unique_subquery: 這種類型用例如一下形式的 in 子查詢來替換 ref:
MySQL
1 |
value in (select primary_key from single_table where some_expr) |
unique_subquery: 只是用來徹底替換子查詢的索引查找函數效率更高了。
index_subquery: 這種鏈接類型相似 unique_subquery。它用子查詢來代替in,不過它用於在子查詢中沒有惟一索引的狀況下,例如如下形式:
MySQL
1 |
value in (select key_column from single_table where some_expr) |
range: 只有在給定範圍的記錄纔會被取出來,利用索引來取得一條記錄。key字段表示使用了哪一個索引。key_len字段包括了使用的鍵的最長部分。這種類型時 ref 字段值是 null。range用於將某個字段和一個定植用如下任何操做符比較時 =, <>, >,>=, <, <=, is null, <=>, between, 或 in:
MySQL
1 |
select * from tbl_name where key_column = 10; select * fromtbl_name where key_column between 10 and 20; select * from tbl_namewhere key_column in (10,20,30); select * from tbl_name wherekey_part1= 10 and key_part2 in (10,20,30); |
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條件後,就再也不搜索更多的記錄了。下面是一個這種類型的查詢例子:
MySQL
1 |
select * from t1 left join t2 on t1.id=t2.id where t2.id isnull; |
假使 t2.id 定義爲 not null。這種狀況下,mysql將會掃描表 t1而且用 t1.id 的值在 t2 中查找記錄。當在 t2中找到一條匹配的記錄時,這就意味着 t2.id 確定不會都是null,就不會再在 t2 中查找相同 id值的其餘記錄了。也能夠這麼說,對於 t1 中的每一個記錄,mysql只須要在t2 中作一次查找,而無論在 t2 中實際有多少匹配的記錄。
MySQL
1 |
range checked for each record (index map: #) |
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 來檢測:
MySQL
1 2 3 4 5 |
explain select tt.ticketnumber, tt.timein, tt.projectreference,tt.estimatedshipdate, tt.actualshipdate, tt.clientid,tt.servicecodes, tt.repetitiveid, tt.currentprocess, tt.currentdppers tt.recordvolume, tt.dpprinted, et.country,et_1.country, do.custname from tt, et, et as et_1, do wherett.submittime is null and tt.actualpc = et.employid andtt.assignedpc = et_1.employid and tt.clientid = do.custnmbr; |
在這個例子中,先作如下假設:
要比較的字段定義以下:
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分析的結果顯示以下:
MySQL
1 2 3 4 5 |
table type possible_keys key key_len ref rows extra et all primarynull null null 74 do all primary null null null 2135 et_1 allprimary 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就能夠在這些字段上更快的是用索引(對isam類型的表來講,除非字段定義徹底同樣,不然不會使用索 引)。在這個前提下,varchar和 char是同樣的除非它們定義的長度不一致。因爲 tt.actualpc 定義爲char(10),et.employid 定義爲 char(15),兩者長度不一致。
爲了解決這個問題,須要用 alter table 來加大 actualpc的長度從10到15個字符:
MySQL
1 |
mysql> alter table tt modify actualpc varchar(15); |
如今 tt.actualpc 和 et.employid 都是 varchar(15)
了。再來執行一次 explain 語句看看結果:
MySQL
1 2 3 4 |
table type possible_keys key key_len ref rows extra tt allassignedpc, null null null 3872 using clientid, where actualpc do all primary null null null 2135 range checked for each record (keymap: 1) et_1 all primary null null null 74 range checked for eachrecord (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
1 |
mysql> alter table tt modify assignedpc varchar(15), ->modify clientid varchar(15); |
如今 explain 的結果以下:
MySQL
1 2 3 4 5 |
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
1 |
mysql> analyze table tt; |
到此爲止,錶鏈接已經優化的很完美了,explain 的結果以下:
MySQL
1 2 3 4 5 |
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分句中用不一樣的次序列出各個表。