mysql5.7版本的explain解析


為什么數據庫中要使用B+tree索引,而不用hash索引?MySQL中的B+tree索引介紹》 

看完以上這篇文章,明白B+tree索引結構,對explain解析更有幫助。

MySQL官網doc文檔: https://dev.mysql.com/doc/refman/5.7/en/explain-output.html 

 

一、建立 t_user 表並建立3個B+tree索引

先在mysql中建個表:

crete table t_user (  
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `username` varchar(36) CHARACTER SET latin1 COLLATE latin1_swedish_ci NULL DEFAULT NULL,
  `age` int(4) NOT NULL DEFAULT 20,
  `birthday_date_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
  `remark` varchar(36) CHARACTER SET latin1 COLLATE latin1_swedish_ci NULL DEFAULT NULL,
  `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  `version` int(4) NOT NULL DEFAULT 0,
  PRIMARY UNIQUE INDEX `idx_name`(`username`) USING BTREE,
  `idx_age_remark`(`age`, `remark`) USING BTREE,
  INDEX idx_create_time(create_time) USING BTREE

) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = latin1 COLLATE = latin1_swedish_ci ROW_FORMAT = DYNAMIC;

 

這個DDL創建了 4 個B+tree索引,我都用黃色背景標出來了。用同樣的DDL語句再復制一個t_user2表。

現在開始使用explain對各種查詢進行分析:

 

二、Explain解析SQL

重點:explain只認select關鍵字,每出現一次select,就生成一個id。所以如果有子查詢或者union之類的,會生成多個id。

Explain關鍵列解釋
select_type   table   Extra
解釋 解釋  No tables used     當SQL中沒有from子句時 
SIMPLE 簡單的select 查詢,不使用 union 及子查詢 system

當表只有一行記錄(等於系統表),並且存儲引擎像MyISAM在統計表數據時是精確的,就會出現system。

像innodb這個存儲引擎在運行中就不會精確統計表數據(這里當然不是指select count(*)這種)。

這是const類型的特列,平時不會出現,這個也可以忽略不計。

Impossible WHERE  當SQL中的where 條件永遠為false時
PRIMARY 最外層的 select 查詢 const 根據主鍵或者唯一二級索引列與常數進行等值匹配時  no matching row in const table 當查詢列表處有MIN或者MAX聚集函數,但是並沒有符合WHERE子句中的搜索條件的記錄時,將會提示該額外信息
UNION UNION 中的第二個或隨后的 select 查詢,不 依賴於外部查詢的結果集 eq_ref 主鍵索引(primary key)或者非空唯一索引(unique not null)等值掃描  Using index 當使用到覆蓋索引時 
UNION RESULT UNION 結果集,臨時表。物化表,是結果緩存內存,臨時表。 ref 當使用普通二級索引(不包含唯一索引)與常量進行等值匹配時  Using index condition 索引條件下推
SUBQUERY 子查詢中的第一個 select 查詢,不依賴於外 部查詢的結果集。 ref_or_null 有時候我們不僅想找出某個二級索引列的值等於某個常數的記錄,還想把該列的值為NULL的記錄也找出來  Using where 需要使用where后面的條件一行行過濾。比如全表掃描,比如回表后又用了where其他條件過濾
DEPENDENT UNION UNION 中的第二個或隨后的 select 查詢,依賴於外部查詢的結果集  index_merge  一般情況下對於某個表的查詢只能使用到一個索引,在某些場景下可以使用索引合並的方式來執行查詢:比如主鍵列范圍查詢,或者其他二級索引等值匹配。  Using join buffer 在連接查詢執行過程中,當被驅動表不能有效的利用索引加快訪問速度,MySQL一般會為其分配一塊名叫join buffer的內存塊來加快查詢速度
DEPENDENT SUBQUERY 子查詢中的第一個 select 查詢,依賴於外部查詢的結果集  unique_subquery 當使用in到子查詢時,而且in的對象是一個主鍵時  Not exists 當我們使用左(外)連接時,如果WHERE子句中包含要求被驅動表的某個列等於NULL值的搜索條件,而且那個列又是不允許存儲NULL值的,那么在該表的執行計划的Extra列就會提示Not exists額外信息
DERIVED 用於 from 子句里有子查詢的情況。 MySQL 會 遞歸執行這些子查詢, 把結果放在臨時表里。 這個是對派生表的物化。  index_subquery 當使用in到子查詢時,而且in的對象是二級索引時    
MATERIALIZED 物化子查詢。這個是對子查詢的物化。  range 當使用了 = < > BETWEEN 等比較符在索引字段上時,注意mysql會把in, like也當成一種range掃描范圍的    
UNCACHEABLE SUBQUERY 結果集不能被緩存的子查詢,必須重新為外層查詢的每一行進行評估,出現極少。  index  如果用到了覆蓋索引中的第二列或以上去select第一列,不需要回表,就是index    
UNCACHEABLE UNION UNION 中的第二個或隨后的select 查詢,屬於不可緩存的子查詢,出現極少。  ALL  全表掃描(full table scan)    
  總結:通過某個小查詢的select_type屬性,就知道了這個小查詢在整個大查詢中扮演了一個什么角色。 總結:

執行計划的一條記錄就代表着MySQL對某個表的執行查詢時的訪問方法/訪問類型,其中的type列就表明了這個訪問方法/訪問類型是個什么東西,是較為重要的一個指標,結果值從最好到最壞依次是:

system > const > eq_ref > ref > fulltext > ref_or_null > index_merge > unique_subquery > index_subquery > range > index > ALL
出現比較多的是:system>const>eq_ref>ref>range>index>ALL

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

回表:回表是個典型的隨機IO,能不回表就不回表,盡量使用主鍵索引、二級覆蓋索引查詢。

  
   

 

三、EXPLAIN的select_type列例子

3.1 select_type = SIMPLE 

簡單的select 查詢,不使用 union 及子查詢。 EXPLAIN select * from t_user;

 

3.2 select_type = PRIMARY 或 UNION 或 UNION RESULT

EXPLAIN select * from t_user union select * from t_user;

 3.3 select_type = SUBQUERY 

EXPLAIN select * from t_user where id in (  select id from t_user2  ) or age = 40;

3.4 select_type = DEPENDENT SUBQUERY 或 DEPENDENT UNION

EXPLAIN warning 查看的例子也包含其中。  EXPLAIN select * from t_user where id in ( select id from t_user where id = 800 union select id from t_user where id = 900 );

 

3.5 select_type = DERIVED

EXPLAIN select * from ( select id, count(*) as c from t_user2 group by id ) t_derived where c > 1;

 3.6 select_type = MATERIALIZED

EXPLAIN select * from t_user where age in ( select age from t_user2 );

 這里的<subquery2>是子查詢的結果集臨時表名,外面的select查詢和這個子查詢結果集物化表,做了一個關聯查詢,所以兩個id都是1.

 3.7 select_type = UNCACHEABLE SUBQUERY

這種情況主要是指用了什么變量,MySQL無法緩存該SQL,出現場景極少。

set @myid = 1043;    EXPLAIN select * from t_user where id = ( select id from t_user2 where id = @myid );

 

 四、EXPLAIN的table列例子

4.1 table = system     

這里沒有例子。。。

4.2 table = const

根據主鍵或者唯一二級索引列與常數進行等值匹配時。 EXPLAIN select * from t_user where id = 1888;

 

 4.3 table = eq_ref 

這個t2是驅動表,t1是被驅動表。就像是訂單表(驅動表)和訂單詳情表(被驅動表),一般訂單表里有一條記錄,而訂單詳情表有N條記錄。

如果對這個被驅動表的訪問方式,是通過等於id主鍵或二級唯一索引這種等值比較,則是eq_ref

EXPLAIN select * from t_user t1 inner join t_user2 t2 on t1.id = t2.id;

 4.4 table = ref

EXPLAIN select * from t_user t1 where age = 50;        通過普通的二級索引查詢。當使用普通二級索引(不包含唯一索引)與常量進行等值匹配時

 4.5 table = ref_or_null

有時候我們不僅想找出某個二級索引列的值等於某個常數的記錄,還想把該列的值為NULL的記錄也找出來。

EXPLAIN select * from t_user t1 WHERE username = 'c7c70eca-903b-44b7-9a76-b3286bfa1d3a' or username is null;

 4.6 table = index_merge    

EXPLAIN select * from t_user t1 WHERE  username = 'c7c70eca-903b-44b7-9a76-b3286bfa1d3a' or age = 10;    使用索引合並的方式查詢,但是一般情況下只會使用到一個二級索引

 4.7 table = unique_subquery

EXPLAIN select * from t_user t1 WHERE t1.ID IN (SELECT t2.ID FROM t_user2 t2 WHERE t1.create_time = t2.create_time) or username = 'c7c70eca-903b-44b7-9a76-b3286bfa1d3a';

當使用in到子查詢時,而且in的對象是一個主鍵時,就會出現unique_subquery。這個例子中使用 show warnings/G 可以看到mysql把in優化成了exists

 

 4.8 table = index_subquery

EXPLAIN select * from t_user t1 WHERE t1.username IN (SELECT t2.username FROM t_user2 t2 WHERE t1.create_time = t2.create_time) or username = 'c7c70eca-903b-44b7-9a76-b3286bfa1d3a';

當使用in到子查詢時,而且in的對象是一個二級索引時,就會出現index_subquery

 4.9 table = range   

 EXPLAIN select * from t_user where age BETWEEN 15 and 18;                 當使用了 = < > BETWEEN 等比較符在索引字段上時,注意mysql會把in, like也當成一種range掃描范圍的

EXPLAIN select * from t_user where username like 'a%';

 

4.10 table = index    

EXPLAIN select age from t_user where remark = 15;        因為有個聯合索引 idx_age_remark(age,remark)  所以這里用了remark列,然后只查詢了age列,這兩個列都是這個聯合二級索引里的列,就成了索引覆蓋,不需要回表,所以這個查詢就是index,如果倒過來where age然后select remark的話就是ref 級別了。

 

 4.11 table = ALL    

EXPLAIN select * from t_user;              不帶任何條件,就是全表掃描

 五、EXPLAIN的possible_keys 與 key

possible_keys是可能用到的索引,key是實際用到的索引。 EXPLAIN select username from t_user where create_time = '2021-06-02 08:19:14' and username like 'bbb%';

在下面這個例子中,可能用到的索引沒有,但實際用到了索引idx_age_remark,因為我們存在一個二級聯合索引 idx_age_remark(age, reamrk)。

EXPLAIN select age from t_user where remark = 10;

 

 六、EXPLAIN的key_len

key_len表示用到的索引長度值。 EXPLAIN select * from t_user where id = 10;   這里用到id主鍵,由於主鍵時 int(11) 所以是int是定長4個字節。

 但是在varchar列中,需要看該列的字符集長度,是不定長的。

比如這里用的utf8mb4是4個字節,這里Length是40,就是 40 * 4 = 160,再加上varchar需要是否為空的標記和長度信息,共占用2個字節,所以這里是162字節:

EXPLAIN select age from t_user where username like 'c7%'; 

username列在db里的設計截圖,是varchar(40)

 數據類型本身占字節長度:

int(11) 4個字節。即使你寫成int(1)也沒用,仍然是占用4個字節
tinyint(3) 1個字節。即使你寫成tinyint(10)也沒用,仍然是占用一個字節
timestamp 4個字節,定長。
datetime 8個字節,定長。
gbk編碼為: 1個字符2個字節
utf8編碼為:1個字符3個字節
utf8mb4編碼為: 1個字符4個字節

 七、EXPLAIN的ref

當使用索引列等值匹配的條件去執行查詢時,也就是在訪問方法是const、eg_ref、ref、ref_or_null、unique_sutbquery、index_subopery其中之一時,ref列展示的就是與索引列作等值匹配的是誰。

EXPLAIN select * from t_user t1 WHERE id = 400;      這個例子演示的是等值匹配。

ref 這一列的值是告訴你SQL中的被比較對象,是和哪個db庫的哪個表的哪個列進行比較。如下例中,就是對mytest庫的t2表的id列進行比較。

EXPLAIN select * from t_user t1 INNER JOIN t_user2 t2 on t1.id = t2.id;     這個例子演示的是連接產生的等值匹配。

 EXPLAIN select * from t_user t1 INNER JOIN t_user2 t2 on t1.username = UPPER(t2.username)     這個例子證明,有時候等值匹配的也可以是個函數 func

 八、EXPLAIN的rows

EXPLAIN select * from t_user where age = 70;    如果查詢優化器決定使用全表掃描的方式對某個表執行查詢時,執行計划的rows列就代表預計需要掃描的行數,如果使用索引來執行查詢時,執行計划的rows列就代表預計掃描的索引記錄行數。

EXPLAIN select * from t_user where username > 'a';         這個例子中,表總數才一萬條,但是EXPLAIN預計需要掃描9815條,所以還不如全表掃描,所以 type = ALL

EXPLAIN select * from t_user where username > 'z';       這個例子中,EXPLAIN預計需要掃描一條記錄就能找到,所以走了 type = range,而不是全表掃描

 

 九、EXPLAIN的filtered

EXPLAIN select * from t_user t1 INNER JOIN t_user2 t2 on t1.id = t2.id WHERE t1.age > 80;     這個例子中,t1表結果集預計會是總數的 20.11%,而 t2表由於是需要查找的記錄全部都是確定值,所以過濾出來的數據是 100%

 十、EXPLAIN的Extra

Extra的種類有幾十個之多,這里只介紹幾種常用的。

No tables used    當SQL中沒有from子句時:EXPLAIN select 1;   

Impossible WHERE    當SQL中的where 條件永遠為false時:EXPLAIN select * from t_user where 1 != 1;       

 

no matching row in const table    當查詢列表處有MIN或者MAX聚集函數,但是並沒有符合WHERE子句中的搜索條件的記錄時,將會提示該額外信息。也就是在該例子中,username = abc的記錄不存在.

EXPLAIN select min(age) from t_user where username = 'abc'; 

 

Using index             當使用到覆蓋索引時    EXPLAIN select remark from t_user where age = 9;   

Using index condition         索引條件下推。即當 username > 'z' 找到一條記錄時,不急着回表,而是看看是不是還符合 username like '%z' ,如果符合,才回表,這就叫索引條件下推。

EXPLAIN select * from t_user where username > 'z' and username like '%a';  

 

 Using where         全表掃描。因為只能依靠where后面的條件一行行過濾    EXPLAIN select * from t_user where birthday_date_time = '1991-11-27 21:58:04';   

Using where          已經使用了 age = 4 這個二級索引找到確切的數據,再回表根據 username > 'c' 這個where條件來過濾數據,這里重點是使用了where條件過濾了

EXPLAIN select * from t_user where age = 4 and username > 'c'; 

 Using join buffer                 在連接查詢執行過程中,當被驅動表不能有效的利用索引加快訪問速度,MySQL一般會為其分配一塊名叫join buffer的內存塊來加快查詢速度

EXPLAIN select * from t_user t1 INNER JOIN t_user2 t2 on t1.birthday_date_time = t2.birthday_date_time; 

Not exists               當我們使用左(外)連接時,如果WHERE子句中包含要求被驅動表的某個列等於NULL值的搜索條件,而且那個列又是不允許存儲NULL值的,那么在該表的執行計划的Extra列就會提示Not exists額外信息

EXPLAIN select * from t_user t1 LEFT JOIN t_user2 t2 on t1.username = t2.username where t2.id is null;

 

Using intersect(...) 交集合並、Using union(...)  並集合並 和 Using sort_union(...) 並集有序合並

如果執行計划的Extra列出現了Using intersect(...)提示,說明准備使用Intersect索引合並的方式執行查詢,括號中的...表示需要進行索引合並的索引名稱;如果出現了Using union(...)提示,說明准備使用Union索引合並的方式執行查詢;出現了Using sort_union(...)提示,說明准備使用Sort-Union索引合並的方式執行查詢。
Zero limit            當我們的LIMIT子句的參數為0時,表示壓根兒不打算從表中讀出任何記錄,將會提示該額外信息。比如 limit 0;
Using filesort      當出現這個,說明SQL執行慢,比如 select * from t_user order by update_time asc limit 10; 這個排序就沒用到索引列。再比如 select * from t_user order by username limit 10;  像這種情況下對結果集中的記錄進行排序是可以使用到索引的。

Using temporary        臨時表。在許多查詢的執行過程中,MySQL可能會借助臨時表來完成一些功能,比如去重、排序之類的。

            EXPLAIN select DISTINCT remark from t_user; 

 

Using temporary與Using filesort同時出現,是因為mysql在處理group by中默認加上了order by,因為只有排序,才能分組。但是因為默認加了order by會導致消耗性能,所以可以自己加上order by null; 這樣手動處理后可以省去Using filesort這一步。

EXPLAIN select birthday_date_time,count(*) from t_user GROUP BY birthday_date_time;  

 

 

十一、EXPLAIN的 json格式,查看更具體的cost耗時信息

想查看explain的里更具體的cost耗時信息,參考《mysql5.7版本explain解析,使用format=json查看cost耗時詳細信息

 

 其他知識

1)UNION關鍵字連接

union關鍵字會創建一張臨時表,但是union all就不會創建一張臨時表,因為:union all 的結果集不用去重!!!

EXPLAIN select id,username from t_user where id < 3 union select id,username from t_user where id > 10 and id < 12;

 

end.

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM