手把手教你徹底理解MySQL的explain關鍵字

數據庫是程序員必備的一項基本技能,基本每次面試必問。對於剛出校門的程序員,你只要學會如何使用就行了,但越往後工作越發現,僅僅會寫sql語句是萬萬不行的。寫出的sql,如果性能不好,達不到要求,可能會阻塞整個系統,那對於整個系統來講是致命的。

所以如何判斷你的sql寫的好不好呢?畢竟只有先知道sql寫的好不好,才能再去考慮如何優化的問題。

MySQL官方就給我們提供了很多sql分析的工具,這裏我們主要說一下EXPLAIN。

以下是基於MySQL5.7.28版本進行分析的,不同版本之間略有差異。

1.1 概念

使用EXPLAIN關鍵字可以模擬優化器執行sql語句,從而知道MySQL是如何處理你的語句,分析你的查詢語句或者表結構的性能瓶頸。

用法:EXPLAIN+ sql語句

EXPLAIN執行後返回的信息如下:

各個字段的大致含義如下:

  • id: SELECT 查詢的標識符. 每個 SELECT 都會自動分配一個唯一的標識符.
  • select_type: SELECT 查詢的類型.
  • table: 查詢的是哪個表
  • partitions: 匹配的分區
  • type: join 類型
  • possible_keys: 此次查詢中可能選用的索引
  • key: 此次查詢中確切使用到的索引.
  • key_len: 查詢優化器使用了索引的字節數.
  • ref: 哪個字段或常數與 key 一起被使用
  • rows: 顯示此查詢一共掃描了多少行. 這個是一個估計值.
  • filtered: 表示此查詢條件所過濾的數據的百分比
  • extra: 額外的信息

1.2 準備工作

新建一個數據庫test,執行下面的sql語句

CREATE TABLE t1(id INT(10) AUTO_INCREMENT,content VARCHAR(100) NULL , PRIMARY KEY (id));
CREATE TABLE t2(id INT(10) AUTO_INCREMENT,content VARCHAR(100) NULL , PRIMARY KEY (id));
CREATE TABLE t3(id INT(10) AUTO_INCREMENT,content VARCHAR(100) NULL , PRIMARY KEY (id));
CREATE TABLE t4(id INT(10) AUTO_INCREMENT,content VARCHAR(100) NULL , PRIMARY KEY (id));
INSERT INTO t1(content) VALUES(CONCAT('t1_',FLOOR(1+RAND()*1000)));
INSERT INTO t2(content) VALUES(CONCAT('t2_',FLOOR(1+RAND()*1000)));
INSERT INTO t3(content) VALUES(CONCAT('t3_',FLOOR(1+RAND()*1000)));
INSERT INTO t4(content) VALUES(CONCAT('t4_',FLOOR(1+RAND()*1000)));

下面一一解釋各列的含義。

1.3 id

select查詢的序列號,包含一組數字,表示查詢中執行select子句的順序或操作表的順序。大致分爲下面幾種情況

(1)id相同,執行順序由上至下

上面的查詢語句,三個id都爲1,具有相同的優先級,執行順序由上而下,具體執行順序由優化器決定,這裏執行順序爲t1,t2,t3。

(2)id不同,數字越大優先級越高

如果sql中存在子查詢,那麼id的序號會遞增,id越大越先被執行。如上圖,執行順序是t3、t1、t2,也就是說,最裏面的子查詢最先執行,由裏往外執行。

在我測試的時候,無意中發現,下面的語句,一個使用的是IN關鍵字,一個使用的=運算符,但使用EXPLAIN執行後,結果天壤之別。

這說明使用IN嵌套子查詢,它是按順序來執行的,也就是說每執行一次最外層子查詢,裏面的子查詢都會被重複執行,這好像和我的理解差很多啊(我一直以爲是先執行最裏面的子查詢,再執行外面的)。

具體可以看看這篇文章,我覺得講的大概算是明白了。https://segmentfault.com/a/1190000005742843。這裏就不再繼續贅述了。

千萬別用IN,使用JOIN或者EXISTS代替它

(3)id存在相同的和不同的

在上面語句的基礎上,增加一個IN的子查詢,執行結果如下

執行順序爲t3、t1、t2、t4。值越大的越先執行,相同值的從上往下執行。

1.4 select_type

select_type表示查詢的類型,主要是爲了區分普通查詢、子查詢、聯合查詢等複雜查詢。分爲以下幾種類型:

(1)SIMPLE

簡單的select查詢,查詢中不包含子查詢或者UNION。

(2)PRIMARY

查詢中若包含任何複雜的子查詢,那麼最外層的查詢被標記爲PRIMARY。

(3)DERIVED

在from子句中包含的子查詢被標記爲DERIVED(衍生),MySQL會遞歸執行這些子查詢,把結果放在臨時表中。

(4)SUBQUERY

在select或where子句中包含了子查詢,該子查詢被標記爲SUBQUERY。

(5)UNION

若第二個select查詢語句出現在UNION之後,則被標記爲UNION。若UNION包含在FROM子句的子查詢中,外層SELECT將被標記爲:DERIVED

(6)UNION RESULT

從UNION表獲取結果的SELECT。

上面的前三種在上一小節已經出現過了,看看後面這三種

可以看到id列出現了一個NULL,這是上面沒講到的。一般來說,特殊情況下,如果某行語句引用了其他多行結果集的並集,則該值可以爲 NULL

1.5 table

這個沒啥好講的,表示這個查詢是基於哪種表的。並不一定是真實存在的表,比如上面出現的DERIVED和<union1,2>,一般來說會出現下面的取值:

(1)<union a,b>:輸出結果中編號爲 a 的行與編號爲 b 的行的結果集的並集。

(2)<derived a>:輸出結果中編號爲 a 的行的結果集,derived 表示這是一個派生結果集,如 FROM 子句中的查詢。

(3)<subquery a>:輸出結果中編號爲 a 的行的結果集,subquery 表示這是一個物化子查詢。

1.6 partitions

查詢時匹配到的分區信息,對於非分區表值爲NULL,當查詢的是分區表時,partitions顯示分區表命中的分區情況。

根據官方文檔,在創建表的時候,指定不同分區存放的id值範圍不同。

插入測試數據,讓id值分佈在四個分區內。

執行查詢輸出結果。


1.7 type

type是查詢的訪問類型,是較爲重要的一個指標,性能從最好到最壞依次是 system > const > eq_ref > ref > fulltext > ref_or_null > index_merge > unique_subquery > index_subquery > range > index > ALL。

一般來說,得保證查詢至少到達range級別,最好能達到ref。

(1)system

當表僅存在一行記錄時(系統表),數據量很少,速度很快,這是一種很特殊的情況,不常見。

(2)const

當你的查詢條件是一個主鍵或者唯一索引(UNION INDEX)並且值是常量的時候,查詢速度非常快,因爲只需要讀一次表。


給t1表的content列增加一個唯一索引

(3)**eq_ref **

除了system和const,性能最好的就是eq_ref了。唯一性索引掃描,對於每個索引鍵,表中只有一條記錄與之匹配。常見於主鍵或唯一索引掃描。

(4)ref

非唯一性索引掃描,返回匹配某個單獨值的所有行。區別於eq_ref,ref表示使用除PRIMARY KEYUNIQUE index 之外的索引,即非唯一索引,查詢的結果可能有多個。可以使用 = 運算符或者<=> 運算符。

在t2表的content列加上普通索引

進行查詢

(5)fulltext

查詢時使用 fulltext 索引。

(6)ref_or_null

對於某個字段既需要關聯條件,也需要null 值的情況下。查詢優化器會選擇用ref_or_null 連接查詢。

(7)index_merge

在查詢過程中需要多個索引組合使用,通常出現在有or 關鍵字的sql 中。

(8)unique_subquery

該聯接類型類似於index_subquery。子查詢中的唯一索引。在某些in子查詢裏,用於替換eq_ref,比如下面的查詢語句

value IN (SELECT primary_key FROM single_table WHERE some_expr)

(9)index_subquery

[圖片上傳中...(19.png-e55cec-1621069077487-0)]
利用索引來關聯子查詢,不再全表掃描。用於非唯一索引,子查詢可以返回重複值。類似於unique_subquery,但用於非唯一索引

value IN (SELECT key_column FROM single_table WHERE some_expr)

(10)range

只檢索給定範圍的行,使用一個索引來選擇行。key 列顯示使用了哪個索引,一般就是在你的where 語句中出現
了between、<、>、in 等的查詢,這種範圍掃描索引掃描比全表掃描要好,因爲它只需要開始於索引的某一點,而
結束語另一點,不用掃描全部索引。

舉個例子,t3表中id字段爲主鍵,有PRIMARY索引,content字段沒有建立索引,查詢時使用id作爲條件,結果如下

使用content作爲條件,結果如下

所以,只有對設置了索引的字段,做範圍檢索 type 纔是 range

(11)index

sql語句使用了索引,但沒有通過索引進行過濾,一般是使用了覆蓋索引或者利用索引進行了排序分組。

index和ALL都是讀全表,區別在於index是遍歷索引樹讀取,ALL是從硬盤讀取。index通常比ALL更快,因爲索引文件通常比數據文件小。

舉個例子,查詢t3表主鍵id,結果如下

(12)ALL

全表掃描,性能最差。

1.8 possible_keys

查詢時可能使用的索引,一個或多個。查詢涉及到的字段上若存在索引,則該索引將被列出。注意是可能,實際查詢時不一定會用到。

1.9 key

查詢時實際使用的索引,沒有使用索引則爲NULL。查詢時若使用了覆蓋索引,則該索引只出現在key字段中。

舉個例子,trb1表中有一個組合索引(age, name),那麼當你的查詢列和索引的個數和順序一致時,查詢結果如下:

1.10 key_len

表示索引中使用的字節數,可通過該列計算查詢中使用的索引的長度。在不損失精確性的情況下,長度越短越好

key_len顯示的值是索引字段可能的最大長度,並非實際使用長度,即key_len是根據表定義計算得到,不是通過表內檢索。

key_len 字段能夠幫你檢查是否充分的利用上了索引。ken_len 越長,說明索引使用的越充分。

注意:key_len只計算where條件中用到的索引長度,而排序和分組即便是用到了索引,也不會計算到key_len中。

舉個例子,有表trb1,存在以下字段,以及一個組合索引idx_age_name

下面查詢語句的執行結果



key_len的值爲153、158、null。如何計算:

①先看索引上字段的類型+長度。比如int=4 ; varchar(50) = 50 ; char(50) = 50。

②如果是varchar 或者char 這種字符串字段,視字符集要乘不同的值,比如utf-8 要乘3,GBK 要乘2。

③varchar 這種動態字符串要加2 個字節。

④允許爲空的字段要加1 個字節。

第一條:key_len = name的字節長度 = 50 * 3 + 2 + 1 = 153

第二條:key_len = age 的字節長度 + name 的字節長度= 4 +1 + ( 50*3 + 2 + 1)= 5 + 153 = 158。(使用的索引更充分,查詢結果更精確,但消耗更大)

第三條:索引失效了。

1.11 ref

顯示索引的哪一列被使用了,常見的取值有:const, func,null,字段名。

  • 當使用常量等值查詢,顯示const
  • 當關聯查詢時,會顯示相應關聯表的關聯字段
  • 如果查詢條件使用了表達式函數,或者條件列發生內部隱式轉換,可能顯示爲func
  • 其他情況null

舉個例子,t3表的content字段有普通索引,下面的查詢語句結果如下

1.12 rows

rows 列表示 MySQL 認爲它執行查詢時可能需要讀取的行數,一般情況下這個值越小越好!

1.13 filtered

filtered 是一個百分比的值,表示符合條件的記錄數的百分比。簡單點說,這個字段表示存儲引擎返回的數據在經過過濾後,剩下滿足條件的記錄數量的比例。

MySQL.5.7版本以前想要顯示filtered需要使用explain extended命令。MySQL.5.7後,默認explain直接顯示partitionsfiltered的信息。

1.14 Extra

其他額外的信息。

(1)Using filesort

說明mysql 會對數據使用一個外部的索引排序,而不是按照表內的索引順序進行讀取。MySQL 中無法利用索引完成的排序操作稱爲“文件排序”。

舉個例子,trb1表建立一個組合索引

下面的查詢出現filesort :

按照組合索引的順序,是name、age、purchased,而上面的查詢語句,沒有使用中間的age,所以在order by的時候索引失效了。通常這種情況是需要進行優化的

修改一下上面的sql語句,讓索引不失效。

(2)Using temporary

使了用臨時表保存中間結果,MySQL 在對查詢結果排序時使用臨時表。常見於排序order by 和分組查詢group by。

這條sql語句用了臨時表,又用了文件排序,在數據量非常大的時候效率是很低的,需要進行優化。

所以在使用group by 和 order by的時候,列的數量和順序儘量和索引的一樣。

(3)Using index

Using index 表示相應的select 操作中使用了覆蓋索引(Covering Index),避免訪問了表的數據行,可以提高效率。

如果同時出現using where,表明索引被用來執行索引鍵值的查找。如果沒有同時出現using where,表明索引只是用來讀取數據而非利用索引執行查找。

還是使用上面的trb1表舉例子

只出現了Using index,說明索引用來讀取數據而不是執行查找。

出現了Using where,說明索引被用來執行查找。

(4)Using where

表示查詢時有索引被用來進行where過濾。

(5)Using join buffer

查詢時使用了連接緩存。

(6)impossible where

查詢語句的where條件總是爲false,舉個例子

一般情況下不會出現這種。

關於Extra字段,有很多取值,這裏就不一一列舉了,具體可以看官方文檔。

參考資料:https://dev.mysql.com/doc/refman/5.7/en/explain-output.html

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