天天看點

MySQL EXPLAIN 完全解讀

EXPLAIN使用

explain可用來分析SQL的執行計劃。格式如下:

示例:

結果輸出展示:

字段 format=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 沒有 附加資訊

結果解讀

id

該語句的唯一辨別。如果explain的結果包括多個id值,則數字越大越先執行;而對于相同id的行,則表示從上往下依次執行。

select_type

查詢類型,有如下幾種取值:

查詢類型 作用
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的第二個或後面的查詢

table

表示目前這一行正在通路哪張表,如果SQL定義了别名,則展示表的别名

partitions

目前查詢比對記錄的分區。對于未分區的表,傳回null

type

連接配接類型,有如下幾種取值,性能從好到壞排序 如下:

1 system:該表隻有一行(相當于系統表),system是const類型的特例

2 const:針對主鍵或唯一索引的等值查詢掃描, 最多隻傳回一行資料. const 查詢速度非常快, 因為它僅僅讀取一次即可

3 eq_ref:當使用了索引的全部組成部分,并且索引是PRIMARY KEY或UNIQUE NOT NULL 才會使用該類型,性能僅次于system及const。

4 ref:當滿足索引的最左字首規則,或者索引不是主鍵也不是唯一索引時才會發生。如果使用的索引隻會比對到少量的行,性能也是不錯的。

TIPS

最左字首原則,指的是索引按照最左優先的方式比對索引。比如建立了一個組合索引(column1, column2, column3),那麼,如果查詢條件是:

•WHERE column1 = 1、WHERE column1= 1 AND column2 = 2、WHERE column1= 1 AND column2 = 2 AND column3 = 3 都可以使用該索引;•WHERE column1 = 2、WHERE column1 = 1 AND column3 = 3就無法比對該索引。

5 fulltext:全文索引

6 ref_or_null:該類型類似于ref,但是MySQL會額外搜尋哪些行包含了NULL。這種類型常見于解析子查詢

7 index_merge:此類型表示使用了索引合并優化,表示一個查詢裡面用到了多個索引

8 unique_subquery:該類型和eq_ref類似,但是使用了IN查詢,且子查詢是主鍵或者唯一索引。例如:

9 index_subquery:和unique_subquery類似,隻是子查詢使用的是非唯一索引

10 range:範圍掃描,表示檢索了指定範圍的行,主要用于有限制的索引掃描。比較常見的範圍掃描是帶有BETWEEN子句或WHERE子句裡有>、>=、<、<=、IS NULL、<=>、BETWEEN、LIKE、IN()等操作符。

11 index:全索引掃描,和ALL類似,隻不過index是全盤掃描了索引的資料。當查詢僅使用索引中的一部分列時,可使用此類型。有兩種場景會觸發:

•如果索引是查詢的覆寫索引,并且索引查詢的資料就可以滿足查詢中所需的所有資料,則隻掃描索引樹。此時,explain的Extra 列的結果是Using index。index通常比ALL快,因為索引的大小通常小于表資料。• 按索引的順序來查找資料行,執行了全表掃描。此時,explain的Extra列的結果不會出現Uses index。

•ALL:全表掃描,性能最差。

possible_keys

展示目前查詢可以使用哪些索引,這一列的資料是在優化過程的早期建立的,是以有些索引可能對于後續優化過程是沒用的。

key

表示MySQL實際選擇的索引

key_len

索引使用的位元組數。由于存儲格式,當字段允許為NULL時,key_len比不允許為空時大1位元組。

key_len計算公式: https://www.cnblogs.com/gomysql/p/4004244.html[1]

ref

表示将哪個字段或常量和key列所使用的字段進行比較。

如果ref是一個函數,則使用的值是函數的結果。要想檢視是哪個函數,可在EXPLAIN語句之後緊跟一個SHOW WARNING語句。

rows

MySQL估算會掃描的行數,數值越小越好。

filtered

表示符合查詢條件的資料百分比,最大100。用rows × filtered可獲得和下一張表連接配接的行數。例如rows = 1000,filtered = 50%,則和下一張表連接配接的行數是500。

TIPS

在MySQL 5.7之前,想要顯示此字段需使用explain extended指令;

MySQL.5.7及更高版本,explain預設就會展示filtered

Extra

展示有關本次查詢的附加資訊,取值如下:

1 Child of 'table' pushed [email protected]

此值隻會在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/[2] ,翻譯 https://www.cnblogs.com/abclife/p/10895624.html[3]

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/[4] ,翻譯 http://www.javacoder.cn/?p=39[5]

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的行後,不會為上一行組合中檢查此表中的更多行。例如:

假設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)的值意味着将考慮索引1、4和5。

示例:下面例子中,name是varchar類型,但是條件給出整數型,涉及到隐式轉換。圖中t2也沒有用到索引,是因為查詢之前我将t2中name字段排序規則改為utf8_bin導緻的連結字段排序規則不比對。

結果:

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)”[6]

19 Rematerialize

用得很少,使用類似如下SQL時,會展示Rematerialize

20 Scanned N databases

表示在處理INFORMATION_SCHEMA表的查詢時,掃描了幾個目錄,N的取值可以是0,1或者all。詳見 “Optimizing INFORMATION_SCHEMA Queries”[7]

21 Select tables optimized away

優化器确定:①最多傳回1行;②要産生該行的資料,要讀取一組确定的行,時會出現此提示。一般在用某些聚合函數通路存在索引的某個字段時,優化器會通過索引直接一次定位到所需要的資料行完成整個查詢時展示,例如下面這條SQL。

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/[8] ,翻譯 https://www.cnblogs.com/abclife/p/10895531.html[9]

27 unique row not found

對于形如 SELECT ... FROM tbl_name 的查詢,但沒有行能夠滿足唯一索引或主鍵查詢的條件

28 Using filesort

當Query 中包含 ORDER BY 操作,而且無法利用索引完成排序操作的時候,MySQL Query Optimizer 不得不選擇相應的排序算法來實作。資料較少時從記憶體排序,否則從磁盤排序。Explain不會顯示的告訴用戶端用哪種排序。官方解釋:“MySQL需要額外的一次傳遞,以找出如何按排序順序檢索行。通過根據聯接類型浏覽所有行并為所有比對WHERE子句的行儲存排序關鍵字和行的指針來完成排序。然後關鍵字被排序,并按排序順序檢索行”

29 Using index

僅使用索引樹中的資訊從表中檢索列資訊,而不必進行其他查找以讀取實際行。當查詢僅使用屬于單個索引的列時,可以使用此政策。例如:

30 Using index condition

表示先按條件過濾索引,過濾完索引後找到所有符合索引條件的資料行,随後用 WHERE 子句中的其他條件去過濾這些資料行。通過這種方式,除非有必要,否則索引資訊将可以延遲“下推”讀取整個行的資料。詳見 “Index Condition Pushdown Optimization”[10] 。例如:

TIPS

•MySQL分成了Server層和引擎層,下推指的是将請求交給引擎層處理。• 了解這個功能,可建立是以INDEX (zipcode, lastname, firstname),并分别用如下指令,

開或者關閉索引條件下推,并對比:

的執行結果。

•index condition pushdown從MySQL 5.6開始支援,是MySQL針對特定場景的優化機制,感興趣的可以看下 https://blog.51cto.com/lee90/2060449[11]

31 Using index for group-by

資料通路和 Using index 一樣,所需資料隻須要讀取索引,當Query 中使用GROUP BY或DISTINCT 子句時,如果分組字段也在索引中,Extra中的資訊就會是 Using index for group-by。詳見 “GROUP BY Optimization”[12]

32 Using index for skip scan

表示使用了Skip Scan。詳見 Skip Scan Range Access Method[13]

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[14]

34 Using MRR

使用了Multi-Range Read優化政策。詳見 “Multi-Range Read Optimization”[15]

35 Using sort_union(...), Using union(...), Using intersect(...)

這些訓示索引掃描如何合并為index_merge連接配接類型。詳見 “Index Merge Optimization”[16] 。

36 Using temporary

為了解決該查詢,MySQL需要建立一個臨時表來儲存結果。如果查詢包含不同列的GROUP BY和 ORDER BY子句,通常會發生這種情況。

37 Using where

如果我們不是讀取表的所有資料,或者不是僅僅通過索引就可以擷取所有需要的資料,則會出現using where資訊

38 Using where with pushed condition

僅用于NDB

39 Zero limit

該查詢有一個limit 0子句,不能選擇任何行

擴充的EXPLAIN

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關鍵詞。

使用示例:

由于SHOW WARNING的結果并不一定是一個有效SQL,也不一定能夠執行(因為裡面包含了很多特殊标記)。特殊标記取值如下:

<auto_key>

自動生成的臨時表key

<cache>(expr)

表達式(例如标量子查詢)執行了一次,并且将值儲存在了記憶體中以備以後使用。對于包括多個值的結果,可能會建立臨時表,你将會看到 

<temporary table>

 的字樣

<exists>(query fragment)

子查詢被轉換為 

EXISTS

<in_optimizer>(query fragment)

這是一個内部優化器對象,對使用者沒有任何意義

<index_lookup>(query fragment)

使用索引查找來處理查詢片段,進而找到合格的行

<if>(condition, expr1, expr2)

如果條件是true,則取expr1,否則取expr2

<is_not_null_test>(expr)

驗證表達式不為NULL的測試

<materialize>(query fragment)

使用子查詢實作

materialized-subquery.col_name

在内部物化臨時表中對col_name的引用,以儲存子查詢的結果

10 

<primary_index_lookup>(query fragment)

使用主鍵來處理查詢片段,進而找到合格的行

11 

<ref_null_helper>(expr)

這是一個内部優化器對象,對使用者沒有任何意義

12 

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”[17]

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的系統變量控制

繼續閱讀