【mysql】explain語句的輸出格式

本文是翻譯而來的,原文地址:EXPLAIN Output Format

explain語句會爲我們顯示select語句的執行計劃相關的信息。

每個在select語句中用到的table,explain語句都會爲其返回一行信息,而且這些table的順序是按照MYSQL查詢時的順序輸出的。MYSQL使用內嵌循環的方式來解決所有連表。這意味着,MYSQL會先從第一張表中讀取一行數據,然後去第二張表中尋找匹配的行,然後以此類推。當所有的table都處理後,MYSQL輸出查詢的字段,然後通過explain輸出的數據表列表回溯,直到它找到一個匹配了最多行的表。下一行從該表開始讀取直到處理下一張表。

如果使用了extended關鍵字,那麼explain會產生額外的信息,這些信息可以使用show warnings語句來查看。(注意:在explain語句中,不能同時使用extended和partitions這兩個關鍵字)

一、explain的輸出字段

這一小節介紹explain輸出的各個字段的含義,下一小節會更加詳細的介紹type和extra字段。

explain輸出的每一行都對應的select查詢所用到的table。每一行的字段和每個字段的含義如下表所示:

字段 含義
id 查詢的標識
select_type 查詢的類型
table 該行信息對應的table
partitions 匹配到的分區
type 連接類型
possible_keys 可以選擇使用的索引
key 實際使用的索引
key_len 實際使用的索引的長度
ref 與索引對照的字段
rows 預估將要掃描的行
filtered 通過條件過濾的行數的佔比
extra 額外信息

id
查詢標識,它是一系列連續的數字。但如果該行表示的是從其他聯合查詢返回的結果,那麼它的值將會是null,而且該行的table字段的值會是這個樣子:<unionM,N>,它表示,改行是從id爲M和N的聯合查詢中返回的結果。
select_type
查詢的類型,它的值有如下幾種:

select_type的值 該值的含義
SIMPLE 簡單的查詢,沒有使用UNION或者子查詢
PRIMARY 最外層的查詢
UNION 在UNION語句中的第二層或者之後的查詢
DEPENDENT UNION UNION語句中的第二層或者之後的查詢,取決於對外的查詢
UNION RESULT UNION查詢的結果
SUBQUERY 第一層子查詢
DEPENDENT SUBQUERY 第一層子查詢,取決於對外的查詢
DERIVED 衍生的查詢
UNCACHEABLE SUBQUERY 不可緩存的子查詢
UNCACHEABLE UNION UNION語句的第二層查詢的select_type是UNCACHEABLE SUBQUERY

DEPENDENT SUBQUERY不同於UNCACHEABLE SUBQUERY。對於DEPENDENT SUBQUERY來說,只有外層的值發生改變時,纔會重新查詢,並返回結果。而UNCACHEABLE SUBQUERY,外層的每一行,都會導致它重新執行查詢。

可緩存的子查詢並不是意味着把查詢結果緩存在緩存池中。子查詢的結果緩存只會發生在查詢期間,當查詢結束後,子查詢在緩存中的數據就會被銷燬。

table
該行所對應的表名稱。但也有可能是如下值:
<unionM,N>:從union語句的結果中查詢,這些union語句的id是從M到N;
<derivedN>:該行是從id爲N的查詢結果中衍生出來的。

partitions
被匹配到的分區。該字段只有當使用關鍵字 PARTITIONS時,纔會出現。對於非分區表,該字段的值是NULL。

type
連接類型。這塊內容詳見下一小節。

possible_keys
該字段顯示哪些索引是本次查詢可以使用的。需要注意的是:該字段完全取決於所在行在explain輸出中的順序。這就意味着,有些索引雖然在該字段中顯示了,但在實際的查詢中,並不會用得上。

如果這個字段的值爲NULL,那就意味着沒有相關的索引。這種情況下,你可能需要通過修改你的where子句,使用那些有建立索引的字段來匹配,以此來優化查詢。也可以自己建立一個新的索引,然後再用explain檢查一下。

可以使用 SHOW INDEX FROM tbl_name 語句來查看錶中有哪些索引。

key
該字段顯示的是在查詢中,實際使用的索引。如果mysql決定使用possible_keys中的一個索引來檢索數據,那麼該字段的值就會在possible_keys字段中。

也有可能,該字段會出現一個不在possible_keys中的值。這種情況一般出現在,possible_keys中的所有索引都不適合用於本次查詢,但存在一個索引,涵蓋了所有要查詢的字段,所以,雖然直接檢索這個索引,就可以得到想要的數據,索引檢索遠遠比數據檢索快得多。

如要要強制使用或者強制忽略某個在possible_keys中的索引,可以在查詢中使用 FORCE INDEX、USE INDEX、IGNORE INDEX來實現。

對於MYISAM或者NDB來說,執行ANALYZE TABLE可以幫助優化器選擇更好的索引。對於MYISAM表,執行myisamchk –analyze和執行ANALYZE TABLE是一樣的。

key_len
該字段顯示本次查詢,mysql使用的索引的長度。如果key字段的值是NULL ,那麼該字段的值也會是NULL 。注意的是:該字段可以幫助你判斷,在一次查詢中,使用了聯合索引中的幾個字段來檢索數據。

ref
該字段顯示哪些字段或者常數與key一起從表中選擇行。

rows
這字段顯示mysql覺得將要有多少行的數據需要被檢索。

對於Innodb表來說,這個字段的值是估算的,並不是總是精確的。

filtered
該字段顯示mysql預估被過濾掉的數據佔比。rows字段顯示預估的行數,rows * filtered / 100 表示的是有多少行數據和前一個表連表。這一字段只有在使用explain extended時才顯示。

extra
該字段顯示mysql在本次查詢中的額外的信息。在下下小節中,會詳細說明該字段的含義。

二、explain的連接類型
explain語句輸出結果中的type字段描述表與表之間是如何連接的。接下來會按從最優到最差列出所有的連接的類型:

system
該表中只有一行數據,這其實是const類型的一個特例。

const
該表中最多有一行數據匹配。由於只有一行數據,所以對於優化器來說,該行中的數據可以當成常量來處理。讀取常量表的數據,只需讀取一次,所以是很快的。
當你匹配主鍵中的所有字段或者唯一鍵中的所有字段時,就會使用const的連接類型。在下面的查詢中,tbl_name表可以被當做是常量表。

SELECT * FROM tbl_name WHERE primary_key=1;

SELECT * FROM tbl_name WHERE primary_key_part1=1 AND primary_key_part2=2;

eq_ref
對於每個來自於前面的表的行組合,從該表中讀取一行。除了system和const類型,這可能是最好的連接類型。當部分主鍵或者部分唯一建被使用時,連接類型就會是eq_ref。

當索引字段用等號(=)來匹配的時候,就會使用eq_ref類型。用來比較的值,可以是常數或者是其他表格的字段。以下列舉了幾個使用eq_ref的列子:

SELECT * FROM ref_table,other_table WHERE ref_table.key_column=other_table.column;

SELECT * FROM ref_table,other_table WHERE ref_table.key_column_part1=other_table.column AND ref_table.key_column_part2=1;

ref
對於每個來自於前面的表的行組合,所有有匹配索引值的行將從這張表中讀取。如果聯接只使用鍵的最左邊的前綴,或如果鍵不是UNIQUE或PRIMARY KEY(換句話說,如果聯接不能基於關鍵字選擇單個行的話)則使用ref。如果使用的鍵僅僅匹配少量行,該聯接類型是不錯的。

當索引字段用等號(=)或者(<=>)來匹配的時候,就會使用ref類型。以下列舉了幾個使用ref的列子:

SELECT * FROM ref_table WHERE key_column=expr;

SELECT * FROM ref_table,other_table WHERE ref_table.key_column=other_table.column;

SELECT * FROM ref_table,other_table WHERE ref_table.key_column_part1=other_table.column AND ref_table.key_column_part2=1;

fulltext
使用全文索引。

ref_or_null
這種連接類型與ref類似,但是mysql會額外判斷是否含有null的行。在子查詢中,優化器最經常使用這種連接類型。下面這個列子,mysql使用的就是ref_or_null

SELECT * FROM ref_table WHERE key_column=expr OR key_column IS NULL;

index_merge
這種類型表明優化器使用了索引合併。這種情況下,key字段的值會是一個列表,列出本次查詢使用的所有索引,key_len字段也會是一個列表,列出每個索引使用的長度。

unique_subquery
在某些子查詢中,會使用這種類型代替ref類型,如下面這種情況:

value IN (SELECT primary_key FROM single_table WHERE some_expr)

unique_subquery只是之中使用索引來代替子查詢,以此提高查詢效率的方法。

index_subquery
這種類型和unique_subquery有點類似。它也是用來代替子查詢,只是沒有唯一索引。如下列子:

value IN (SELECT key_column FROM single_table WHERE some_expr)

range
使用一個索引來檢索一個範圍的數據。key字段顯示哪個索引被使用,key_len顯示使用索引的長度,這種情況下,ref字段的值爲null。

當索引使用=, <>, >, >=, <, <=, IS NULL, <=>, BETWEEN, 或者 IN()來匹配時,使用range類型。如下列子:

SELECT * FROM tbl_name WHERE key_column = 10;

SELECT * FROM tbl_name WHERE key_column BETWEEN 10 and 20;

SELECT * FROM tbl_name WHERE key_column IN (10,20,30);

SELECT * FROM tbl_name WHERE key_part1= 10 AND key_part2 IN (10,20,30);

index
這種類型和all類型有點類似,只不過這種方式掃描的是索引樹。有兩種情況會產生這種類型:

  1. 如果要查詢的列都在一個索引中,那麼mysql將只掃描索引樹來獲取數據。這種情況下,extra字段會顯示”Using index”。index會比all類型快的原因在於,索引樹的大小一般比真實的數據表小。
  2. 雖然是一個全表掃描,但會按着索引樹的順序,先掃描索引再去表中獲取數據。這種情況下,extra字段不會顯示”Using index”。

all
對於每個來自於先前的表的行組合,進行一次全表掃描。如果表是第一個沒有標記爲const的表,那麼性能會比較差,而且在一般在所有的情況,性能都會極差。一般可以通過添加索引來避免產生all類型。

三、explain的extra字段信息

extra字段提供mysql在本次查詢中的額外信息。下面列出該字段可能會出現的值,如果你想讓你的查詢儘可能的快,一定要留心該字段中出現”Using filesort”和”Using temporary”。

* Child of ‘table’ pushed join@1*
在NDB引擎中,該表被當做是一個其他表的子表。只有在NDB引擎7.2.0版本後會出現。

const row not found
比如這類查詢:SELECT …… FROM tbl_name,然後表是空的。

Distinct
當mysql只需要查找唯一的值,如果它找到一行匹配的數據,它就會停止尋找這類數據。

Full scan on NULL key
在子查詢中,如果優化沒有使用索引來找出數據。

Impossible Having
having字段一直返回的是false,或者不能選擇任何行。

Impossible WHERE noticed after reading const tables
mysql把所有的讀完所有的常量表後發現where一直都是沒有作用的。

No matching min/max row
沒有相關的行來記錄這類查詢的數據:SELECT MIN(…) FROM … WHERE condition。

no matching row in const table
在一個連表查詢中 ,所連接的表是空表,或者該表中沒有匹配的數據。

No tables used
查詢沒有from字段。

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中匹配數據。當mysql在t2中id與t1相等的行,但它知道t2的id將永遠不會是null,那麼mysql就不會繼續在t2表中需找與t1表id相同的行了。換句話說,每掃描一行t1的數據,當去t2中尋找匹配的數據時,mysql只需要找到一行id與t1相同的數據,就不需要繼續掃描了,不論t2表中有多少行的id與t1中相等。

Range checked for each record (index map : N)
mysql發現沒有一個好的索引可以使用,但是當前面表中的數據已知之後,有些索引是可以用的。這種情況下,mysql會爲前面表的每個行組合判斷是否可以使用rang或者index_merge的方法去匹配行數據。這種方式並不快,但是總比沒有使用索引的連表來得快。

索引是從1開始編號的,展示的順序和使用show index時是一樣的。索引映射值N是一個位掩碼錶明哪些索引會被考慮使用。舉個列子,如果值是0x19(二進制是11001),則表明,索引編號爲1、4和5會被考慮使用。

Scanned N databases
這表明當查詢表中數據時,有多少個目錄掃面了服務器執行,該值可以是0、1或者all。

Select tables optimized away
該查詢只包含聚合的函數(min()、max()),那些只是用一個索引就可以解決的,或者是count(*),而且沒有groub by字段。那麼優化器只需要返回一行數據。

Skip_open_table,Open_frm_only,Open_trigger_only,Open_full_table
這些值表明在表中查詢數據時,文件打開的優化選擇,這些值的含義如下:

  1. Skip_open_table:數據表對應的文件不需要被打開,它的信息已經變成變量包含檢索數據庫目錄的查詢中。
  2. Open_frm_only:只需要打開數據表對應的.frm文件就可以了。
  3. Open_trigger_only:只需要打開數據表對應的.TRG文件就可以了。
  4. Open_full_table:沒有優化的信息可以使用,表格對應的.frm、.MYD和.MYI文件都需要被打開。

unique row not found
對於這樣的查詢:select …… from tbl_name,沒有任何行可以滿足唯一索引或者主鍵的條件。

Using filesort
MySQL需要額外的一次操作,以找出如何按排序順序檢索行。這次排序是通過掃面所有的行,根據連接類型,先把那些符合刷選添加的行對應的key的值和行號存起來。然後排序這些key,排序完以後,安裝key的順序把行數據返回。

Using index
字段信息只需要從索引樹種獲取,而不需要做一次額外的查詢去讀取真實的行數據。當查詢中需要的字段都在索引中時,可以使用這種方法。

如果這時候Extra字段還顯示“Using where”,這意味着該索引被用來查找行數據對應的指針。如果沒有Using where ,優化器只需要讀取索引樹。舉個例子,如果索引覆蓋了要查詢的字段,那麼優化器則是掃描索引數,而不是用它來查詢數據。

對於InnoDB的表來說,它有用戶定義的聚合索引,這種索引即使Extra字段中沒有顯示“Using where”時,也會被使用。這種情況出現在,當type字段是index,且key字段是PRIMARY時。

Using index for group-by
獲取表數據的方式和Using index類似,Using index for group-by表明mysql發現有一個索引可以返回group by子句或者distinct子句的所有字段,這樣就不用爲存儲排序的key而額外掃描一次表。另外,對於分組操作,索引是最有效的方法,只有少數的索引需要被讀取。

Using join buffer
在join前面的表中的部分數據被存放到緩存中,這樣,在後面的表查詢中就可以從緩存中獲取數據。

Using sort_union(…),Using union(…),Using interesect(…)
當type爲index_merge時,這些信息表明索引查找出來的數據是如何合併的。

Using temporary
爲了執行本次查詢,mysql需要建一個臨時表來存放結果集。出現這種情況,有代表性的列子是,查詢中包含group by和order by子句,而且這兩個子句包含的字段是不同的。

Using where
WHERE子句用於限制哪一個行匹配下一個表或發送到客戶。除非你專門從表中索取或檢查所有行,如果Extra值不爲Using where並且表聯接類型爲ALL或index,查詢可能會有一些錯誤。即使where子句中所有的字段都包含在一個索引中 ,也可以能會看到Using where,因爲有些字段可能會是NULL。

Using where with pushed condition
這種情況只會出現在NDB表中。

四、explain輸出解釋

從explain的輸出結果中,你可以得到一個很好地啓示。它會大致的告訴你,mysql會檢查多少行來完成本次查詢。如果你通過系統變量max_join_size來限制查詢,那麼explain的輸出也可以告訴你在select語句中的哪些表被查詢了,哪些表被放棄了。

以下列子展示一個多表的連表查詢,通過explain提供的信息,是如何漸漸地被優化的。

假設你有一個查詢語句如下,然後你想通過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)
ttd ClientID CHAR(10)
et EMPLOYID CHAR(15)
do CUSTNMBR CHAR(15)

該表有如下索引

表名 索引
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,ClientID,ActualPC NULL NULL NULL 3872 Range checked for each record (index map: 0x23)

由於所有的type都是All,這個結果表明,對於每個表,mysql都產生了笛卡爾積。這表明,每個組合的行,都會使用相當長的時間,由於每張表的每行數據都要被檢索。對於上述情況,會產生 74 × 2135 × 74 × 3872 = 45,268,558,720行。如果表格更大一些,你可以想象這會執行多長的時間。

這裏的一個問題是,如果這些字段有相同的類型,那麼mysql可以使用索引來使查詢更加的高效。VARCHAR和CHAR,如果定義的長度相同,那麼會被認爲是相同的類型。tt.ActuralPC被定義爲CHAR(10),et.EMPLOYID被定爲CHAR(15),所以長度不匹配。爲了解決這個問題,用alter table命令把ActualPC的長度變成15。

ALTER TABLE tt MODIFY ActualPC VARCHAR(15);

現在,tt.ActuralPC和et.EMPLOYID的長度都是15了。再次執行上述語句,結果如下:

table type possible_keys key key_len ref rows Extra
tt ALL AssignedPC,ClientID,ActualPC NULL NULL NULL 3872 Using where
do ALL PRIMARY NULL NULL NULL 2135 Range checked for each record (index map: 0x1)
et_1 ALL PRIMARY NULL NULL NULL 74 Range checked for each record (index map: 0x1)
et eq_ref PRIMARY PRIMARY 15 tt.ActualPC 1

這並不完美,但是已經好多了。它產生的行數,比之前少了74倍。這個版本需要執行幾秒鐘。

第二可以做如下改變,消除tt.AssignedPC = et_1.EMPLOYID 和tt.ClientID = do.CUSTNMBR的長度不匹配問題。

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,ClientID,ActualPC ActualPC 15 et.EMPLOYID 52 Using index
et_1 eq_ref PRIMARY PRIMARY 15 tt.AssignedPC 1
do eq_ref PRIMARY PRIMARY 15 tt.ClientID 1

到目前爲止,這個查詢已經儘可能的優化了。剩下的問題是,mysql會默認假設tt.ActualPC字段上的數據是均勻分佈的,然後tt表的情況並不是這樣。幸運的是,這是很容易告訴mysql去分析鍵的分佈。

ANALYZE TABLE tt

加上額外的索引信息,該連表會變得完美。explain的輸出結果如下:

table type possible_keys key key_len ref rows Extra
tt ALL AssignedPC,ClientID,ActualPC NULL NULL NULL 3872 Using index
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優化器的分析得出的。判斷這個rows字段的值是否接近於實際的值,可以將該值與本次查詢返回的行數進行比較。如果這兩個值相差很大,你可以在select子句中加入STRAIGHT_JOIN語句,然後嘗試把join的表的順序變換一下,以此來減小差距。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章