MYSQL 之EXPLAIN 語法

EXPLAINtbl_name

或者: mysql

EXPLAIN SELECTselect_options

EXPLAIN 語句能夠被看成 DESCRIBE 的同義詞來用,也能夠用來獲取一個MySQL要執行的 SELECT 語句的相關信息。 sql

  • EXPLAIN tbl_name 語法和 DESCRIBE tbl_name 或 SHOW COLUMNS FROM tbl_name同樣。
  • 當在一個 SELECT 語句前使用關鍵字 EXPLAIN 時,MYSQL會解釋了即將如何運行該 SELECT 語句,它顯示了表如何鏈接、鏈接的順序等信息。

本章節主要講述了第二種 EXPLAIN 用法。 函數

在 EXPLAIN 的幫助下,您就知道何時該給表添加索引,以使用索引來查找記錄從而讓 SELECT 運行更快。 性能

若是因爲不恰當使用索引而引發一些問題的話,能夠運行 ANALYZE TABLE 來更新該表的統計信息,例如鍵的基數,它能幫您在優化方面作出更好的選擇。詳情請看"ANALYZE TABLE Syntax"優化

您還能夠查看優化程序是否以最佳的順序來鏈接數據表。爲了讓優化程序按照 SELECT語句中的表名的順序作鏈接,能夠在查詢的開始使用 SELECT STRAIGHT_JOIN 而不僅是SELECT。 spa

EXPLAIN 返回了一行記錄,它包括了 SELECT 語句中用到的各個表的信息。這些表在結果中按照MySQL即將執行的查詢中讀取的順序列出來。MySQL用一次掃描屢次鏈接(single-sweep, multi-join) 的方法來解決鏈接。這意味着MySQL從第一個表中讀取一條記錄,而後在第二個表中查找到對應的記錄,而後在第三個表中查找,依次類推。當全部的表都掃描完了,它輸出選擇的字段而且回溯全部的表,直到找不到爲止,由於有的表中可能有多條匹配的記錄下一條記錄將從該表讀取,再從下一個表開始繼續處理。 指針

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

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 就是 const 表了:
SELECT * FROMtbl_nameWHEREprimary_key=1;
SELECT * FROMtbl_nameWHEREprimary_key_part1=1 ANDprimary_key_part2=2;
eq_ref
從該表中會有一行記錄被讀取出來以和從前一個表中讀取出來的記錄作聯合。與 const 類型不一樣的是,這是最好的鏈接類型。它用在索引全部部分都用於作鏈接而且這個索引是一個 PRIMARY KEY 或 UNIQUE 類型。eq_ref 能夠用於在進行"="作比較時檢索字段。比較的值能夠是固定值或者是表達式,表達式中可使用表裏的字段,它們在讀表以前已經準備好了。如下的幾個例子中,MySQL使用了 eq_ref 鏈接來處理 ref_table:

SELECT * FROMref_table,other_tableWHEREref_table.key_column=other_table.column;
SELECT * FROMref_table,other_tableWHEREref_table.key_column_part1=other_table.columnANDref_table.key_column_part2=1;
ref
該表中全部符合檢索值的記錄都會被取出來和從上一個表中取出來的記錄做聯合。ref 用於鏈接程序使用鍵的最左前綴或者是該鍵不是 PRIMARY KEY 或 UNIQUE索引(換句話說,就是鏈接程序沒法根據鍵值只取得一條記錄)的狀況。當根據鍵值只查詢到少數幾條匹配的記錄時,這就是一個不錯的鏈接類型。ref 還能夠用於檢索字段使用 = 操做符來比較的時候。如下的幾個例子中,MySQL將使用ref 來處理 ref_table:
SELECT * FROMref_tableWHEREkey_column=expr;
SELECT * FROMref_table,other_tableWHEREref_table.key_column=other_table.column;
SELECT * FROMref_table,other_tableWHEREref_table.key_column_part1=other_table.columnANDref_table.key_column_part2=1;
ref_or_null
這種鏈接類型相似 ref,不一樣的是MySQL會在檢索的時候額外的搜索包含 NULL值的記錄。這種鏈接類型的優化是從MySQL 4.1.1開始的,它常常用於子查詢。在如下的例子中,MySQL使用 ref_or_null 類型來處理 ref_table:
SELECT * FROMref_tableWHEREkey_column=exprORkey_columnIS NULL;


index_merge
這種鏈接類型意味着使用了 Index Merge 優化方法。這種狀況下,key字段包括了全部使用的索引,key_len 包括了使用的鍵的最長部分。
unique_subquery
這種類型用例如一下形式的 IN 子查詢來替換 ref:
valueIN (SELECTprimary_keyFROMsingle_tableWHEREsome_expr)

unique_subquery 只是用來徹底替換子查詢的索引查找函數效率更高了。 索引

index_subquery
這種鏈接類型相似 unique_subquery。它用子查詢來代替 IN,不過它用於在子查詢中沒有惟一索引的狀況下,例如如下形式:
valueIN (SELECTkey_columnFROMsingle_tableWHEREsome_expr)
range
只有在給定範圍的記錄纔會被取出來,利用索引來取得一條記錄。key 字段表示使用了哪一個索引。key_len 字段包括了使用的鍵的最長部分。這種類型時 ref 字段值是 NULL。range 用於將某個字段和一個定植用如下任何操做符比較時 =, <>, >,>=, <, <=, IS NULL, <=>, BETWEEN, 或 IN:
SELECT * FROMtbl_nameWHEREkey_column= 10;

SELECT * FROMtbl_nameWHEREkey_columnBETWEEN 10 and 20;

SELECT * FROMtbl_nameWHEREkey_columnIN (10,20,30);

SELECT * FROMtbl_nameWHEREkey_part1= 10 ANDkey_part2IN (10,20,30);
index
鏈接類型跟 ALL 同樣,不一樣的是它只掃描索引樹。它一般會比 ALL 快點,由於索引文件一般比數據文件小。MySQL在查詢的字段知識單獨的索引的一部分的狀況下使用這種鏈接類型。
ALL
將對該表作所有掃描以和從前一個表中取得的記錄做聯合。這時候若是第一個表沒有被標識爲 const 的話就不大好了,在其餘狀況下一般是很是糟糕的。正常地,能夠經過增長索引使得能從表中更快的取得記錄以免 ALL。
possible_keys
possible_keys 字段是指MySQL在搜索表記錄時可能使用哪一個索引。注意,這個字段徹底獨立於 EXPLAIN 顯示的表順序。這就意味着 possible_keys 裏面所包含的索引可能在實際的使用中沒用到。若是這個字段的值是 NULL,就表示沒有索引被用到。這種狀況下,就能夠檢查 WHERE 子句中哪些字段那些字段適合增長索引以提升查詢的性能。就這樣,建立一下索引,而後再用 EXPLAIN 檢查一下。想看錶都有什麼索引,能夠經過 SHOW INDEX FROM tbl_name 來看。 key
key 字段顯示了MySQL實際上要用的索引。當沒有任何索引被用到的時候,這個字段的值就是 NULL。想要讓MySQL強行使用或者忽略在 possible_keys 字段中的索引列表,能夠在查詢語句中使用關鍵字FORCE INDEX, USE INDEX, 或 IGNORE INDEX。若是是 MyISAM 和 BDB 類型表,可使用 ANALYZE TABLE 來幫助分析使用使用哪一個索引更好。若是是 MyISAM 類型表,運行命令 myisamchk --analyze 也是同樣的效果。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 IS NULL;

假使 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 條件的記錄的要排序的鍵和指向記錄的指針存儲起來。這些鍵已經排完序了,對應的記錄也會按照排好的順序取出來。Using index
字段的信息直接從索引樹中的信息取得,而再也不去掃描實際的記錄。這種策略用於查詢時的字段是一個獨立索引的一部分。Using temporary
MySQL須要建立臨時表存儲結果以完成查詢。這種狀況一般發生在查詢時包含了GROUP BY 和 ORDER BY 子句,它以不一樣的方式列出了各個字段。Using where
WHERE 子句將用來限制哪些記錄匹配了下一個表或者發送給客戶端。除非你特別地想要取得或者檢查表種的全部記錄,不然的話當查詢的 Extra 字段值不是 Using where 而且錶鏈接類型是 ALL 或 index 時可能表示有問題。

若是你想要讓查詢儘量的快,那麼就應該注意 Extra 字段的值爲Using filesort 和 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.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;

在這個例子中,先作如下假設:


  • 要比較的字段定義以下:
    Table Column Column Type
    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  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就能夠在這些字段上更快的是用索引(對 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 語句看看結果:

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 分句中用不一樣的次序列出各個表。

相關文章
相關標籤/搜索