來源:persister
連結:http://www.blogjava.net/persister/archive/2008/10/27/236813.html
在 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表了:
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:
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:
select * from ref_table where key_column=expr;
select * fromref_table,other_table whereref_table.key_column=other_table.column;
ref_or_null: 這種連接配接類型類似 ref,不同的是mysql會在檢索的時候額外的搜尋包含null 值的記錄。這種連接配接類型的優化是從mysql4.1.1開始的,它經常用于子查詢。在以下的例子中,mysql使用ref_or_null 類型來處理 ref_table:
select * from ref_table where key_column=expr or key_column is null;
unique_subquery: 這種類型用例如一下形式的 in 子查詢來替換 ref:
value in (select primary_key from single_table where some_expr)
unique_subquery: 隻是用來完全替換子查詢的索引查找函數效率更高了。
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 * 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條件後,就不再搜尋更多的記錄了。下面是一個這種類型的查詢例子:
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 中實際有多少比對的記錄。
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 來檢測:
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分析的結果顯示如下:
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> alter table tt modify actualpc varchar(15);
現在 tt.actualpc 和 et.employid 都是 varchar(15)了。再來執行一次 explain 語句看看結果:
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> alter table tt modify assignedpc varchar(15), ->modify clientid varchar(15);
現在 explain 的結果如下:
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 的結果如下:
tt all assignedpc null null null 3872 using clientid, where actualpc
et eq_ref primary primary 15 tt.actualpc 1
請注意,explain 結果中的 rows字段的值也是mysql的連接配接優化程式大緻猜測的,請檢查這個值跟真實值是否基本一緻。如果不是,可以通過在select 語句中使用 straight_join 來取得更好的性能,同時可以試着在from分句中用不同的次序列出各個表。