Mysql之Explain關鍵字及常見的優化手段


Explain關鍵字字段描述:

Explain關鍵字字段詳情描述

id

我們寫的查詢語句一般都以SELECT關鍵字開頭,比較簡單的查詢語句里只有一個SELECT關鍵字,但是下邊兩種情況下在一條查詢語句中會出現多個SELECT關鍵字:

  1)查詢中包含子查詢的情況

  2)查詢中包含UNION語句的情況

查詢語句中每出現一個SELECT關鍵字,MySQL就會為它分配一個唯一的id值。這個id值就是EXPLAIN語句的第一個列。對於連接查詢來說,一個SELECT關鍵字后邊的FROM子句中可以跟隨多個表,所以在連接查詢的執行計划中,每個表都會對應一條記錄,但是這些記錄的id值都是相同的。

在連接查詢的執行計划中,每個表都會對應一條記錄,這些記錄的id列的值是相同的,
出現在前邊的表是驅動表,出現在后邊的表是被驅動表

對於包含子查詢的查詢語句來說,就可能涉及多個SELECT關鍵字,所以在包含子查詢的查詢語句的執行計划中,每個SELECT關鍵字都會對應一個唯一的id值,比如這樣:

mysql> explain select * from t1 where a in (select a from t2) or c = 'c';
+----+-------------+-------+------------+-------+---------------+---------+---------+------+------+----------+-------------+
| id | select_type | table | partitions | type  | possible_keys |    key  | key_len |  ref | rows | filtered |   Extra     |
+----+-------------+-------+------------+-------+---------------+---------+---------+------+------+----------+-------------+
| 1  |  PRIMARY    |    t1 |   NULL     |   ALL |     NULL      |   NULL  |   NULL  | NULL |   8  |   100.00 | Using where |
| 2  |  SUBQUERY   |    t2 |   NULL     | index |    PRIMARY    | PRIMARY |    4    | NULL |   8  |   100.00 | Using index |
+----+-------------+-------+------------+-------+---------------+---------+---------+------+------+----------+-------------+
2 rows in set, 1 warning (0.00 sec)

但是這里大家需要特別注意,查詢優化器可能對涉及子查詢的查詢語句進行重寫,從而轉換為連接查詢。所以如果我們想知道查詢優化器對某個包含子查詢的語句是否進行了重寫,直接查看執行計划就好了,比如說:

mysql> explain select * from t1 where a in (select a from t2);
+----+-------------+-------+------------+--------+---------------+---------+---------+------------+------+----------+-------------+
| id | select_type | table | partitions |   type | possible_keys |   key   | key_len |   ref      | rows | filtered |   Extra     |
+----+-------------+-------+------------+--------+---------------+---------+---------+------------+------+----------+-------------+
| 1  |    SIMPLE   |  t1   |   NULL     |   ALL  |    PRIMARY    |   NULL  |    NULL |    NULL    |   8  |   100.00 |    NULL     |
| 1  |    SIMPLE   |  t2   |   NULL     | eq_ref |    PRIMARY    | PRIMARY |   4     | luban.t1.a |   1  |   100.00 | Using index |
+----+-------------+-------+------------+--------+---------------+---------+---------+------------+------+----------+-------------+
2 rows in set, 1 warning (0.00 sec)

  

可以看到,雖然我們的查詢語句是一個子查詢,但是執行計划中t1和t2表對應的記錄的id值全部是1,這就表明了查詢優化器將子查詢轉換為了連接查詢。

對於包含UNION子句的查詢語句來說,每個SELECT關鍵字對應一個id值也是沒錯的,不過還是有點兒特別的東西,比方說下邊這個查詢:

mysql> explain select * from t1 union select * from t2;
+----+--------------+------------+------------+------+---------------+------+---------+------+------+----------+-----------------+
| id | select_type  |   table    | partitions | type | possible_keys | key  | key_len |  ref | rows | filtered |     Extra       |
+----+--------------+------------+------------+------+---------------+------+---------+------+------+----------+-----------------+
| 1  |   PRIMARY    |     t1     |    NULL    |  ALL |    NULL       | NULL |   NULL  | NULL |   8  |   100.00 |       NULL      |
| 2  |   UNION      |     t2     |    NULL    |  ALL |    NULL       | NULL |   NULL  | NULL |   8  |   100.00 |       NULL      |
|NULL| UNION RESULT | <union1,2> |    NULL    |  ALL |    NULL       | NULL |   NULL  | NULL | NULL |   NULL   | Using temporary |
+----+--------------+------------+------------+------+---------------+------+---------+------+------+----------+-----------------+
3 rows in set, 1 warning (0.00 sec)

這個語句的執行計划的第三條記錄是什么?為什么id值是NULL?UNION會把多個查詢的結果集合並起來並對結果集中的記錄進行去重,怎么去重呢?MySQL使用的是內部的臨時表。正如上邊的查詢計划中所示,UNION子句是為了把id為1的查詢和id為2的查詢的結果集合並起來並去重,所以在內部創建了一個名為的臨時表(就是執行計划第三條記錄的table列的名稱),id為NULL表明這個臨時表是為了合並兩個查詢的結果集而創建的。

跟UNION對比起來,UNION ALL就不需要為最終的結果集進行去重,它只是單純的把多個查詢的結果集中的記錄合並成一個並返回給用戶,所以也就不需要使用臨時表。所以在包含UNION ALL子句的查詢的執行計划中,就沒有那個id為NULL的記錄。

select_type

每一個SELECT關鍵字代表的小查詢都定義了一個稱之為select_type的屬性,意思是我們只要知道了某個小查詢的select_type屬性,就知道了這個小查詢在整個大查詢中扮演了一個什么角色。

SIMPLE:查詢語句中不包含UNION或者子查詢的查詢都算作是SIMPLE類型、連接查詢也算是SIMPLE類型。
PRIMARY:對於包含UNION、UNION ALL或者子查詢的大查詢來說,它是由幾個小查詢組成的,其中最左邊的那個查詢的select_type值就是PRIMARY
UNION:對於包含UNION或者UNION ALL的大查詢來說,它是由幾個小查詢組成的,其中除了最左邊的那個小查詢以外,其余的小查詢的select_type值就是UNION
UNION RESULT:MySQL選擇使用臨時表來完成UNION查詢的去重工作,針對該臨時表的查詢的select_type就是UNION RESULT
SUBQUERY:非相關子查詢,由於select_type為SUBQUERY的子查詢由於會被物化,所以只需要執行一遍。
DEPENDENT SUBQUERY:相關子查詢,select_type為DEPENDENT SUBQUERY的查詢可能會被執行多次
DERIVED:子查詢是以物化的方式執行的
MATERIALIZED:當查詢優化器在執行包含子查詢的語句時,選擇將子查詢物化之后與外層查詢進行連接查詢時,該子查詢對應的select_type屬性就是MATERIALIZED

type

 

1)system

當表中只有一條記錄並且該表使用的存儲引擎的統計數據是精確的,比如MyISAM、Memory,那么對該表的訪問方法就是system。

2)const

當我們根據主鍵或者唯一二級索引列與常數進行等值匹配時,對單表的訪問方法就是const。

3)eq_ref

在連接查詢時,如果被驅動表是通過主鍵或者唯一二級索引列等值匹配的方式進行訪問的(如果該主鍵或者唯一二級索引是聯合索引的話,所有的索引列都必須進行等值比較),則對該被驅動表的訪問方法就是eq_ref

4)ref

當通過普通的二級索引列與常量進行等值匹配時來查詢某個表,那么對該表的訪問方法就可能是ref。

5)ref_or_null

當對普通二級索引進行等值匹配查詢,該索引列的值也可以是NULL值時,那么對該表的訪問方法就可能是ref_or_null

6)index_merge

索引合並

7)unique_subquery

如果查詢優化器決定將IN子查詢轉換為EXISTS子查詢,而且子查詢可以使用到主鍵進行等值匹配的話,那么該子查詢執行計划的type列的值就是unique_subquery。

8)index_subquery

index_subquery與unique_subquery類似,只不過訪問子查詢中的表時使用的是普通的索引。

9)range

10)index

當我們可以使用覆蓋索引,但需要掃描全部的索引記錄時,該表的訪問方法就是index。

11)ALL

全表掃描

SQL性能排序:

system > const > eq_ref > ref > ref_or_null > index_merge > unique_subquery > index_subquery > range > index > ALL

possible_keys 和 key

possible_keys列表示在某個查詢語句中,對某個表執行單表查詢時可能用到的索引有哪些,key列表示實際用到的索引有哪些。

不過有一點比較特別,就是在使用index訪問方法來查詢某個表時,possible_keys列是空的,而key列展示的是實際使用到的索引

possible_keys列中的值並不是越多越好,可能使用的索引越多,查詢優化器計算查詢成本時就得花費更長時
間,所以如果可以的話,盡量刪除那些用不到的索引

key_len

key_len列表示當優化器決定使用某個索引執行查詢時,該索引記錄的最大長度,它是由這三個部分構成的:

1、對於使用固定長度類型的索引列來說,它實際占用的存儲空間的最大長度就是該固定值,對於指定字符集的變長類型的索引列來說,比如某個索引列的類型是VARCHAR(100),使用的字符集是utf8,那么該列實際占用的最大存儲空間就是100 × 3 = 300個字節。
2、如果該索引列可以存儲NULL值,則key_len比不可以存儲NULL值時多1個字節。
3、對於變長字段來說,都會有2個字節的空間來存儲該變長列的實際長度。

ref

當使用索引列等值匹配的條件去執行查詢時,也就是在訪問方法是const、eq_ref、ref、ref_or_null、unique_subquery、index_subquery其中之一時,ref列展示的就是與索引列作等值匹配的東西是什么,比如只是一個常數或者是某個列。

rows

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

filtered

代表查詢優化器預測在這掃描的記錄中,有多少條記錄滿足其余的搜索條件。

mysql> explain select * from t1 where a > 1 and e = 1;
+----+-------------+-------+------------+-------+---------------+---------+---------+------+------+----------+-------------+
| id | select_type | table | partitions |  type | possible_keys |    key  | key_len |  ref | rows | filtered |    Extra    |
+----+-------------+-------+------------+-------+---------------+---------+---------+------+------+----------+-------------+
| 1  |   SIMPLE    |   t1  |    NULL    | range |   PRIMARY     | PRIMARY |    4    | NULL |    8 |   11.11  | Using where |
+----+-------------+-------+------------+-------+---------------+---------+---------+------+------+----------+-------------+
1 row in set, 1 warning (0.00 sec)

從執行計划的key列中可以看出來,該查詢使用PRIMARY索引來執行查詢,從rows列可以看出滿足a > 1的記錄有8條。執行計划的filtered列就代表查詢優化器預測在這8條記錄中,有多少條記錄滿足其余的搜索條件,也就是e= 1這個條件的百分比。此處filtered列的值是11.11,說明查詢優化器預測在8條記錄中有11.11%的記錄滿足e = 1 這個條件。

對於單表查詢來說,這個filtered列的值沒什么意義,我們更關注在連接查詢中驅動表對應的執行計划記錄的filtered值。

mysql> explain select * from t1 join t2 on t1.a = t2.a where t1.e = 1;
+----+-------------+-------+------------+--------+---------------+---------+---------+------------+------+----------+-------------+
| id | select_type | table | partitions |   type | possible_keys |   key   | key_len |    ref     | rows | filtered |    Extra    |
+----+-------------+-------+------------+--------+---------------+---------+---------+------------+------+----------+-------------+
| 1  |   SIMPLE    |   t1  |    NULL    |    ALL |    PRIMARY    |   NULL  |   NULL  |    NULL    |   9  |   11.11  | Using where |
| 1  |   SIMPLE    |   t2  |    NULL    | eq_ref |    PRIMARY    | PRIMARY |   4     | luban.t1.a |   1  |   100.00 |    NULL     |
+----+-------------+-------+------------+--------+---------------+---------+---------+------------+------+----------+-------------+
2 rows in set, 1 warning (0.00 sec)

從執行計划中可以看出來,查詢優化器打算把t1當作驅動表,t2當作被驅動表。我們可以看到驅動表t1表的執行計划的rows列為9, filtered列為11.11,這意味着驅動表t1表經過條件過濾后有9 × 11.11% = 0.9999條記錄,這說明還要對被驅動表執行大約1次查詢。

Extra

Extra列是用來說明一些額外信息的,我們可以通過這些額外信息來更准確的理解MySQL到底將如何執行給定的查詢語句。

  No tables used:當查詢語句的沒有FROM子句時將會提示該額外信息。

  Impossible WHERE:查詢語句的WHERE子句永遠為FALSE時將會提示該額外信息。

  No matching min/max row:當查詢列表處有MIN或者MAX聚集函數,但是並沒有符合WHERE子句中的搜索條件的記錄時,將會提示該額外信息

  Using index:當我們的查詢列表以及搜索條件中只包含屬於某個索引的列,也就是在可以使用索引覆蓋的情況下,在Extra列將會提示該額外信息

  Using index condition:有些搜索條件中雖然出現了索引列,但卻不能使用到索引(在MySQL 5.6版本后加入的新特性)

  Using where:當我們使用全表掃描來執行對某個表的查詢,並且該語句的WHERE子句中有針對該表的搜索條件時,在Extra列中會提示上述額外信息

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

  Using filesort:很多情況下排序操作無法使用到索引,只能在內存中(記錄較少的時候)或者磁盤中(記錄較多的時候)進行排序,這種在內存中或者磁盤上進行排序的方式統稱為文件排序(英文名:filesort)。如果某個查詢需要使用文件排序的方式執行查詢,就會在執行計划的Extra列中顯示Using filesort提示。

  Using temporary:在許多查詢的執行過程中,MySQL可能會借助臨時表來完成一些功能,比如去重、排序之類的,比如我們在執行許多包含DISTINCT、GROUP BY、UNION等子句的查詢過程中,如果不能有效利用索引來完成查詢,MySQL很有可能尋求通過建立內部的臨時表來執行查詢。如果查詢中使用到了內部的臨時表,在執行計划的Extra列將會顯示Using temporary提示。即有Using temporary,又有Using filesort,因為group by默認會先排序

  Start temporary、End temporary:查詢優化器會優先嘗試將IN子查詢轉換成semi-join,而semi-join又有好多種執行策略,當執行策略為DuplicateWeedout時,也就是通過建立臨時表來實現為外層查詢中的記錄進行去重操作時,驅動表查詢執行計划的Extra列將顯示Start temporary提示,被驅動表查詢執行計划的Extra列將顯示End temporary提示

  FirstMatch(表名):在將In子查詢轉為semi-join時,如果采用的是FirstMatch執行策略,則在被驅動表執行計划的Extra列就是顯示FirstMatch(tbl_name)提示

總結

性能 按type排序:

system > const > eq_ref > ref > ref_or_null > index_merge > unique_subquery > index_subquery > range >index > ALL

性能按Extra排序:

Using index:用了覆蓋索引
Using index condition:用了條件索引(索引下推)
Using where:從索引查出來數據后繼續用where條件過濾
Using join buffer (Block Nested Loop):join的時候利用了join buffer(優化策略:去除外連接、增大join buffer大小)
Using filesort:用了文件排序,排序的時候沒有用到索引
Using temporary:用了臨時表(優化策略:增加條件以減少結果集、增加索引,思路就是要么減少結果集、增加索引、思路就是要 么減少待排序的數量,要么就提前排好序)
Start temporary, End temporary:子查詢的時候,可以優化成半連接,但是使用的是通過臨時表來去重
FirstMatch(tbl_name):子查詢的時候,可以優化成半連接,但是使用的是直接進行數據比較來去重

常見的優化手段:

1. SQL語句中IN包含的值不應過多,不能超過200個,200個以內查詢優化器計算成本時比較精准,超過200個是估算的成本,另外建議能用between就不要用in,這樣就可以使用range索引了。
2. SELECT語句務必指明字段名稱:SELECT * 增加很多不必要的消耗(cpu、io、內存、網絡帶寬);增加了使用覆蓋索引的可能性;當表結構發生改變時,前斷也需要更新。所以要求直接在select后面接上字段名。
3. 當只需要一條數據的時候,使用limit 1
4. 排序時注意是否能用到索引
5. 使用or時如果沒有用到索引,可以改為union all 或者union
6. 如果in不能用到索引,可以改成exists看是否能用到索引
7. 使用合理的分頁方式以提高分頁的效率
8. 不建議使用%前綴模糊查詢
9. 避免在where子句中對字段進行表達式操作
10. 避免隱式類型轉換
11. 對於聯合索引來說,要遵守最左前綴法則
12. 必要時可以使用force index來強制查詢走某個索引
13. 對於聯合索引來說,如果存在范圍查詢,比如between,>,<等條件時,會造成后面的索引字段失效。
14. 盡量使用inner join,避免left join,讓查詢優化器來自動選擇小表作為驅動表
15. 必要時刻可以使用straight_join來指定驅動表,前提條件是本身是inner join

 


免責聲明!

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



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