EXPLAIN做爲MySQL的性能分析神器,讀懂其結果是頗有必要的,然而我在各類搜索引擎上居然找不到特別完整的解讀。都是隻有重點,沒有細節(例如type的取值不全、Extra缺少完整的介紹等)。html
因此,我肝了將近一個星期,整理了一下。這應該是全網最全面、最細緻的EXPLAIN解讀文章了,下面是全文。java
文章比較長,建議收藏。mysql
TIPS本文基於MySQL 8.0編寫,理論支持MySQL 5.0及更高版本。面試
explain可用來分析SQL的執行計劃。格式以下:算法
{EXPLAIN | DESCRIBE | DESC} tbl_name [col_name | wild] {EXPLAIN | DESCRIBE | DESC} [explain_type] {explainable_stmt | FOR CONNECTION connection_id} {EXPLAIN | DESCRIBE | DESC} ANALYZE select_statement explain_type: { FORMAT = format_name } format_name: { TRADITIONAL | JSON | TREE } explainable_stmt: { SELECT statement | TABLE statement | DELETE statement | INSERT statement | REPLACE statement | UPDATE statement }
示例:sql
EXPLAIN format = TRADITIONAL json 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;
結果輸出展現:json
id | select_id | 該語句的惟一標識 |
---|---|---|
select_type | 無 | 查詢類型 |
table | table_name | 表名 |
partitions | partitions | 匹配的分區 |
type | access_type | 聯接類型 |
possible_keys | possible_keys | 可能的索引選擇 |
key | key | 實際選擇的索引 |
key_len | key_length | 索引的長度 |
ref | ref | 索引的哪一列被引用了 |
rows | rows | 估計要掃描的行 |
filtered | filtered | 表示符合查詢條件的數據百分比 |
Extra | 沒有 | 附加信息 |
該語句的惟一標識。若是explain的結果包括多個id值,則數字越大越先執行;而對於相同id的行,則表示從上往下依次執行。緩存
查詢類型,有以下幾種取值:服務器
查詢類型 | 做用 |
---|---|
SIMPLE | 簡單查詢(未使用UNION或子查詢) |
PRIMARY | 最外層的查詢 |
UNION | 在UNION中的第二個和隨後的SELECT被標記爲UNION。若是UNION被FROM子句中的子查詢包含,那麼它的第一個SELECT會被標記爲DERIVED。 |
DEPENDENT | UNION UNION中的第二個或後面的查詢,依賴了外面的查詢 |
UNION | RESULT UNION的結果 |
SUBQUERY | 子查詢中的第一個 SELECT |
DEPENDENT | SUBQUERY 子查詢中的第一個 SELECT,依賴了外面的查詢 |
DERIVED | 用來表示包含在FROM子句的子查詢中的SELECT,MySQL會遞歸執行並將結果放到一個臨時表中。MySQL內部將其稱爲是Derived table(派生表),由於該臨時表是從子查詢派生出來的 |
DEPENDENT | DERIVED 派生表,依賴了其餘的表 |
MATERIALIZED | 物化子查詢 |
UNCACHEABLE | SUBQUERY 子查詢,結果沒法緩存,必須針對外部查詢的每一行從新評估 |
UNCACHEABLE | UNION UNION屬於UNCACHEABLE SUBQUERY的第二個或後面的查詢 |
表示當前這一行正在訪問哪張表,若是SQL定義了別名,則展現表的別名併發
當前查詢匹配記錄的分區。對於未分區的表,返回null
鏈接類型,有以下幾種取值,性能從好到壞排序 以下:
1 system:該表只有一行(至關於系統表),system是const類型的特例
2 const:針對主鍵或惟一索引的等值查詢掃描, 最多隻返回一行數據. const 查詢速度很是快, 由於它僅僅讀取一次便可
3 eq_ref:當使用了索引的所有組成部分,而且索引是PRIMARY KEY或UNIQUE NOT NULL 纔會使用該類型,性能僅次於system及const。
-- 多表關聯查詢,單行匹配 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;
4 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;
TIPS
最左前綴原則,指的是索引按照最左優先的方式匹配索引。好比建立了一個組合索引(column1, column2, column3),那麼,若是查詢條件是:
- WHERE column1 = 一、WHERE column1= 1 AND column2 = 二、WHERE column1= 1 AND column2 = 2 AND column3 = 3 均可以使用該索引;
- WHERE column1 = 二、WHERE column1 = 1 AND column3 = 3就沒法匹配該索引。
5 fulltext:全文索引
6 ref_or_null:該類型相似於ref,可是MySQL會額外搜索哪些行包含了NULL。這種類型常見於解析子查詢
SELECT * FROM ref_table WHERE key_column=expr OR key_column IS NULL;
7 index_merge:此類型表示使用了索引合併優化,表示一個查詢裏面用到了多個索引
8 unique_subquery:該類型和eq_ref相似,可是使用了IN查詢,且子查詢是主鍵或者惟一索引。例如:
value IN (SELECT primary_key FROM single_table WHERE some_expr)
9 index_subquery:和unique_subquery相似,只是子查詢使用的是非惟一索引
value IN (SELECT key_column FROM single_table WHERE some_expr)
10 range:範圍掃描,表示檢索了指定範圍的行,主要用於有限制的索引掃描。比較常見的範圍掃描是帶有BETWEEN子句或WHERE子句裏有>、>=、<、<=、IS NULL、<=>、BETWEEN、LIKE、IN()等操做符。
SELECT * FROM tbl_name WHERE key_column BETWEEN 10 and 20; SELECT * FROM tbl_name WHERE key_column IN (10,20,30);
11 index:全索引掃描,和ALL相似,只不過index是全盤掃描了索引的數據。當查詢僅使用索引中的一部分列時,可以使用此類型。有兩種場景會觸發:
展現當前查詢可使用哪些索引,這一列的數據是在優化過程的早期建立的,所以有些索引可能對於後續優化過程是沒用的。
表示MySQL實際選擇的索引
索引使用的字節數。因爲存儲格式,當字段容許爲NULL時,key_len比不容許爲空時大1字節。
key_len計算公式: https://www.cnblogs.com/gomysql/p/4004244.html
表示將哪一個字段或常量和key列所使用的字段進行比較。
若是ref是一個函數,則使用的值是函數的結果。要想查看是哪一個函數,可在EXPLAIN語句以後緊跟一個SHOW WARNING語句。
MySQL估算會掃描的行數,數值越小越好。
表示符合查詢條件的數據百分比,最大100。用rows × filtered可得到和下一張錶鏈接的行數。例如rows = 1000,filtered = 50%,則和下一張錶鏈接的行數是500。
TIPS在MySQL 5.7以前,想要顯示此字段需使用explain extended命令;
MySQL.5.7及更高版本,explain默認就會展現filtered
展現有關本次查詢的附加信息,取值以下:
1 Child of 'table' pushed join@1
此值只會在NDB Cluster下出現。
2 const row not found
例如查詢語句SELECT ... FROM tbl_name,而表是空的
3 Deleting all rows
對於DELETE語句,某些引擎(例如MyISAM)支持以一種簡單而快速的方式刪除全部的數據,若是使用了這種優化,則顯示此值
4 Distinct
查找distinct值,當找到第一個匹配的行後,將中止爲當前行組合搜索更多行
5 FirstMatch(tbl_name)
當前使用了半鏈接FirstMatch策略,詳見 https://mariadb.com/kb/en/firstmatch-strategy/ ,翻譯 https://www.cnblogs.com/abclife/p/10895624.html
6 Full scan on NULL key
子查詢中的一種優化方式,在沒法經過索引訪問null值的時候使用
7 Impossible HAVING
HAVING子句始終爲false,不會命中任何行
8 Impossible WHERE
WHERE子句始終爲false,不會命中任何行
9 Impossible WHERE noticed after reading const tables
MySQL已經讀取了全部const(或system)表,並發現WHERE子句始終爲false
10 LooseScan(m..n)
當前使用了半鏈接LooseScan策略,詳見 https://mariadb.com/kb/en/loosescan-strategy/ ,翻譯 http://www.javacoder.cn/?p=39
11 No matching min/max row
沒有任何能知足例如 SELECT MIN(...) FROM ... WHERE condition 中的condition的行
12 no matching row in const table
對於關聯查詢,存在一個空表,或者沒有行可以知足惟一索引條件
13 No matching rows after partition pruning
對於DELETE或UPDATE語句,優化器在partition pruning(分區修剪)以後,找不到要delete或update的內容
14 No tables used
當此查詢沒有FROM子句或擁有FROM DUAL子句時出現。例如:explain select 1
15 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中找到一個匹配的行,它會知道t2.id永遠不會爲NULL,而且不會掃描t2中具備相同id值的其他行。也就是說,對於t1中的每一行,MySQL只須要在t2中只執行一次查找,而不考慮在t2中實際匹配的行數。
在MySQL 8.0.17及更高版本中,若是出現此提示,還可表示形如 NOT IN (subquery) 或 NOT EXISTS (subquery) 的WHERE條件已經在內部轉換爲反鏈接。這將刪除子查詢並將其表放入最頂層的查詢計劃中,從而改進查詢的開銷。經過合併半鏈接和反聯接,優化器能夠更加自由地對執行計劃中的表從新排序,在某些狀況下,可以讓查詢提速。你能夠經過在EXPLAIN語句後緊跟一個SHOW WARNING語句,並分析結果中的Message列,從而查看什麼時候對該查詢執行了反聯接轉換。
Note兩表關聯只返回主表的數據,而且只返回主表與子表沒關聯上的數據,這種鏈接就叫反鏈接
16 Plan isn't ready yet
使用了EXPLAIN FOR CONNECTION,當優化器還沒有完成爲在指定鏈接中爲執行的語句建立執行計劃時, 就會出現此值。
17 Range checked for each record (index map: N)
MySQL沒有找到合適的索引去使用,可是去檢查是否可使用range或index_merge來檢索行時,會出現此提示。index map N索引的編號從1開始,按照與表的SHOW INDEX所示相同的順序。 索引映射值N是指示哪些索引是候選的位掩碼值。 例如0x19(二進制11001)的值意味着將考慮索引一、4和5。
示例:下面例子中,name是varchar類型,可是條件給出整數型,涉及到隱式轉換。 圖中t2也沒有用到索引,是由於查詢以前我將t2中name字段排序規則改成utf8_bin致使的連接字段排序規則不匹配。
explain select a.* from t1 a left join t2 b on t1.name = t2.name where t2.name = 2;
結果:
id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
---|---|---|---|---|---|---|---|---|---|---|---|
1 | SIMPLE | t2 | NULL | ALL | idx_name | NULL | NULL | NULL | 9 | 11.11 | Using where |
1 | SIMPLE | t1 | NULL | ALL | idx_name | NULL | NULL | NULL | 5 | 11.11 | Range checked for each record (index map: 0x8) |
18 Recursive
出現了遞歸查詢。詳見 「WITH (Common Table Expressions)」
19 Rematerialize
用得不多,使用相似以下SQL時,會展現Rematerialize
SELECT ... FROM t, LATERAL (derived table that refers to t) AS dt ...
20 Scanned N databases
表示在處理INFORMATION_SCHEMA表的查詢時,掃描了幾個目錄,N的取值能夠是0,1或者all。詳見 「Optimizing INFORMATION_SCHEMA Queries」
21 Select tables optimized away
優化器肯定:①最多返回1行;②要產生該行的數據,要讀取一組肯定的行,時會出現此提示。通常在用某些聚合函數訪問存在索引的某個字段時,優化器會經過索引直接一次定位到所須要的數據行完成整個查詢時展現,例以下面這條SQL。
explain select min(id) from t1;
22 Skip_open_table, Open_frm_only, Open_full_table
這些值表示適用於INFORMATION_SCHEMA表查詢的文件打開優化;
23 Skip_open_table:無需打開表文件,信息已經經過掃描數據字典得到
24 Open_frm_only:僅須要讀取數據字典以獲取表信息
25 Open_full_table:未優化的信息查找。表信息必須從數據字典以及表文件中讀取
26 Start temporary, End temporary
表示臨時表使用Duplicate Weedout策略,詳見 https://mariadb.com/kb/en/duplicateweedout-strategy/ ,翻譯 https://www.cnblogs.com/abclife/p/10895531.html
27 unique row not found
對於形如 SELECT ... FROM tbl_name 的查詢,但沒有行可以知足惟一索引或主鍵查詢的條件
28 Using filesort
當Query 中包含 ORDER BY 操做,並且沒法利用索引完成排序操做的時候,MySQL Query Optimizer 不得不選擇相應的排序算法來實現。數據較少時從內存排序,不然從磁盤排序。Explain不會顯示的告訴客戶端用哪一種排序。官方解釋:「MySQL須要額外的一次傳遞,以找出如何按排序順序檢索行。經過根據聯接類型瀏覽全部行併爲全部匹配WHERE子句的行保存排序關鍵字和行的指針來完成排序。而後關鍵字被排序,並按排序順序檢索行」
29 Using index
僅使用索引樹中的信息從表中檢索列信息,而沒必要進行其餘查找以讀取實際行。當查詢僅使用屬於單個索引的列時,可使用此策略。例如:
explain SELECT id FROM t
30 Using index condition
表示先按條件過濾索引,過濾完索引後找到全部符合索引條件的數據行,隨後用 WHERE 子句中的其餘條件去過濾這些數據行。經過這種方式,除非有必要,不然索引信息將能夠延遲「下推」讀取整個行的數據。詳見 「Index Condition Pushdown Optimization」 。例如:
TIPS
- MySQL分紅了Server層和引擎層,下推指的是將請求交給引擎層處理。
理解這個功能,可建立因此INDEX (zipcode, lastname, firstname),並分別用以下指令,
SET optimizer_switch = 'index_condition_pushdown=off'; SET optimizer_switch = 'index_condition_pushdown=on';開或者關閉索引條件下推,並對比:
explain SELECT * FROM people WHERE zipcode='95054' AND lastname LIKE '%etrunia%' AND address LIKE '%Main Street%';的執行結果。
- index condition pushdown從MySQL 5.6開始支持,是MySQL針對特定場景的優化機制,感興趣的能夠看下 https://blog.51cto.com/lee90/2060449
31 Using index for group-by
數據訪問和 Using index 同樣,所需數據只需要讀取索引,當Query 中使用GROUP BY或DISTINCT 子句時,若是分組字段也在索引中,Extra中的信息就會是 Using index for group-by。詳見 「GROUP BY Optimization」
-- name字段有索引 explain SELECT name FROM t1 group by name
32 Using index for skip scan
表示使用了Skip Scan。詳見 Skip Scan Range Access Method
33 Using join buffer (Block Nested Loop), Using join buffer (Batched Key Access)
使用Block Nested Loop或Batched Key Access算法提升join的性能。詳見 https://www.cnblogs.com/chenpingzhao/p/6720531.html
34 Using MRR
使用了Multi-Range Read優化策略。詳見 「Multi-Range Read Optimization」
35 Using sort_union(...), Using union(...), Using intersect(...)
這些指示索引掃描如何合併爲index_merge鏈接類型。詳見 「Index Merge Optimization」 。
36 Using temporary
爲了解決該查詢,MySQL須要建立一個臨時表來保存結果。若是查詢包含不一樣列的GROUP BY和 ORDER BY子句,一般會發生這種狀況。
-- name無索引 explain SELECT name FROM t1 group by name
37 Using where
若是咱們不是讀取表的全部數據,或者不是僅僅經過索引就能夠獲取全部須要的數據,則會出現using where信息
explain SELECT * FROM t1 where id > 5
38 Using where with pushed condition
僅用於NDB
39 Zero limit
該查詢有一個limit 0子句,不能選擇任何行
explain SELECT name FROM resource_template limit 0
EXPLAIN可產生額外的擴展信息,可經過在EXPLAIN語句後緊跟一條SHOW WARNING語句查看擴展信息。
TIPS
- 在MySQL 8.0.12及更高版本,擴展信息可用於SELECT、DELETE、INSERT、REPLACE、UPDATE語句;在MySQL 8.0.12以前,擴展信息僅適用於SELECT語句;
- 在MySQL 5.6及更低版本,需使用EXPLAIN EXTENDED xxx語句;而從MySQL 5.7開始,無需添加EXTENDED關鍵詞。
使用示例:
mysql> EXPLAIN SELECT t1.a, t1.a IN (SELECT t2.a FROM t2) FROM t1\G *************************** 1. row *************************** id: 1 select_type: PRIMARY table: t1 type: index possible_keys: NULL key: PRIMARY key_len: 4 ref: NULL rows: 4 filtered: 100.00 Extra: Using index *************************** 2. row *************************** id: 2 select_type: SUBQUERY table: t2 type: index possible_keys: a key: a key_len: 5 ref: NULL rows: 3 filtered: 100.00 Extra: Using index 2 rows in set, 1 warning (0.00 sec) mysql> SHOW WARNINGS\G *************************** 1. row *************************** Level: Note Code: 1003 Message: /* select#1 */ select `test`.`t1`.`a` AS `a`, <in_optimizer>(`test`.`t1`.`a`,`test`.`t1`.`a` in ( <materialize> (/* select#2 */ select `test`.`t2`.`a` from `test`.`t2` where 1 having 1 ), <primary_index_lookup>(`test`.`t1`.`a` in <temporary table> on <auto_key> where ((`test`.`t1`.`a` = `materialized-subquery`.`a`))))) AS `t1.a IN (SELECT t2.a FROM t2)` from `test`.`t1` 1 row in set (0.00 sec)
因爲SHOW WARNING的結果並不必定是一個有效SQL,也不必定可以執行(由於裏面包含了不少特殊標記)。特殊標記取值以下:
1 <auto_key>
自動生成的臨時表key
2 <cache>(expr)
表達式(例如標量子查詢)執行了一次,而且將值保存在了內存中以備之後使用。對於包括多個值的結果,可能會建立臨時表,你將會看到 <temporary table>
的字樣
3 <exists>(query fragment)
子查詢被轉換爲 EXISTS
4 <in_optimizer>(query fragment)
這是一個內部優化器對象,對用戶沒有任何意義
5 <index_lookup>(query fragment)
使用索引查找來處理查詢片斷,從而找到合格的行
6 <if>(condition, expr1, expr2)
若是條件是true,則取expr1,不然取expr2
7 <is_not_null_test>(expr)
驗證表達式不爲NULL的測試
8 <materialize>(query fragment)
使用子查詢實現
9 materialized-subquery.col_name
在內部物化臨時表中對col_name的引用,以保存子查詢的結果
10 <primary_index_lookup>(query fragment)
使用主鍵來處理查詢片斷,從而找到合格的行
11 <ref_null_helper>(expr)
這是一個內部優化器對象,對用戶沒有任何意義
12 /* select#N */ select_stmt
SELECT與非擴展的EXPLAIN輸出中id=N的那行關聯
13 outer_tables semi join (inner_tables)
半鏈接操做。inner_tables展現未拉出的表。詳見 「Optimizing Subqueries, Derived Tables, and View References with Semijoin Transformations」
14 <temporary table>
表示建立了內部臨時表而緩存中間結果
當某些表是const或system類型時,這些表中的列所涉及的表達式將由優化器儘早評估,而且不屬於所顯示語句的一部分。可是,當使用FORMAT=JSON時,某些const表的訪問將顯示爲ref。
多數狀況下,你能夠經過計算磁盤的搜索次數來估算查詢性能。對於比較小的表,一般能夠在一次磁盤搜索中找到行(由於索引可能已經被緩存了),而對於更大的表,你可使用B-tree索引進行估算:你須要進行多少次查找才能找到行:log(row_count) / log(index_block_length / 3 * 2 / (index_length + data_pointer_length)) + 1
在MySQL中,index_block_length一般是1024字節,數據指針通常是4字節。比方說,有一個500,000的表,key是3字節,那麼根據計算公式 log(500,000)/log(1024/3*2/(3+4)) + 1 = 4
次搜索。
該索引將須要500,000 * 7 * 3/2 = 5.2MB的存儲空間(假設典型的索引緩存的填充率是2/3),所以你能夠在內存中存放更多索引,可能只要一到兩個調用就能夠找到想要的行了。
可是,對於寫操做,你須要四個搜索請求來查找在何處放置新的索引值,而後一般須要2次搜索來更新索引並寫入行。
前面的討論並不意味着你的應用性能會由於log N而緩慢降低。只要內容被OS或MySQL服務器緩存,隨着表的變大,只會稍微變慢。在數據量變得太大而沒法緩存後,將會變慢不少,直到你的應用程序受到磁盤搜索約束(按照log N增加)。爲了不這種狀況,能夠根據數據的增加而增長key的。對於MyISAM表,key的緩存大小由名爲key_buffer_size的系統變量控制,詳見 Section 5.1.1, 「Configuring the Server」