MySQL EXPLAIN 執行計劃詳解

一 .介紹

  EXPLAIN 命令用於SQL語句的查詢執行計劃。這條命令的輸出結果可以讓咱們瞭解MySQL 優化器是如何執行SQL 語句的。這條命令並無提供任何調整建議,但它可以提供重要的信息幫助你作出調優決策。html

先解析一條sql語句,你能夠看出現什麼內容python

?
1
EXPLAIN SELECT  * FROM person,dept WHERE person.dept_id = dept.did and person.salary >20000

下面我們詳細的介紹一下 查詢計劃的結果列:mysql

二. id : 查詢序列號

  查詢序號即爲sql語句執行順序算法

?
1
EXPLAIN select * from person where dept_id =( select did from dept where dname = 'python' );

從 2 個表中查詢,對應輸出 2 行,每行對應一個表, id 列表示執行順序,id 越大,越先執行,id 相同時,由上至下執行。sql

三.select_type : 查詢類型

  select_type 列提供了 對錶的查詢類型。最多見的值包括SIMPLE、PRIMARY、DERIVED 和UNION。其餘可能的值還有 UNION RESULTSUBQUERY 等等.服務器

 2.1  simple 簡單查詢 (沒有union和子查詢)

  對於不包含子查詢和其餘複雜語法的簡單查詢,這是一個常見的類型。mysql優化

?
1
EXPLAIN SELECT * FROM person;

 2.2 primary  最外層查詢 (在存在子查詢的語句中,最外面的select查詢就是primary)

  這是爲更復雜的查詢而建立的首要表(也就是最外層的表)。這個類型一般能夠在DERIVED 和 UNION 類型混合使用時見到。函數

 2.3 derived   子查詢(在FROM列表中包含的子查詢)

  當一個表不是一個物理表時,那麼這個就被叫作DERIVED性能

?
1
EXPLAIN SELECT * FROM ( SELECT * FROM person LIMIT 5) AS s

 2.4 subquery   映射爲子查詢(在SELECT或WHERE列表中包含了子查詢)

  這個select-type 的值是爲使用子查詢而定義的.優化

?
1
EXPLAIN SELECT person.*,( select 2 from person as p2) FROM person where dept_id = ( select did from dept where dname= 'python' );

 2.5  union 聯合

?
1
EXPLAIN SELECT * FROM person union all select * from person ;

2.6  union result  使用聯合的結果

?
1
EXPLAIN SELECT * FROM person union  select * from person ;

四. table 輸出的行所用的表

?
1
EXPLAIN SELECT * FROM person;

注意: table 列是EXPLAIN 命令輸出結果中的一個單獨行的惟一標識符。這個值多是表名、表的別名或者一個爲查詢產生臨時表的標識符,如派生表、子查詢或集合。

五. type 鏈接類型

  type 列表明表示 查詢計劃的鏈接類型, 有多個參數,先從最佳類型到最差類型介紹 重要且困難

  性能: null > system/const > eq_ref > ref > ref_or_null  >index_merge >  range > index >  all 

4.1 type=NULL 在優化過程當中就已獲得結果,不用再訪問表或索引。

?
1
EXPLAIN SELECT max (id) FROM person;

4.2 type=const/system 常量

  在整個查詢過程當中這個表最多隻會有一條匹配的行,好比主鍵 id=1 就確定只有一行;

表最多有一個匹配行,const用於比較primary key 或者unique索引。由於只匹配一行數據,因此必定是用到primary key 或者unique 狀況下才會是const,看下面這條語句

?
1
EXPLAIN SELECT * FROM person where id =2;

因此說能夠理解爲const是最優化的。

 

4.3 type=eq_ref  使用有惟一性 索引查找(主鍵或惟一性索引)

 對於eq_ref的解釋,mysql手冊是這樣說的:"對於每一個來自於前面的表的行組合,從該表中讀取一行。這多是最好的聯接類型,除了const類型。它用在一個索引的全部部分被聯接使用而且索引是UNIQUE或PRIMARY KEY"。eq_ref能夠用於使用=比較帶索引的列。看下面的語句 

?
1
EXPAIN select * from person,dept where person.id = dept.did;

 獲得的結果是下圖所示。很明顯,mysql使用eq_ref聯接來處理 dept 表。  

4.4 type=ref 非惟一性索引訪問

  這是一種索引訪問(有時也叫作索引查找),它返回全部匹配某個單個值的行,然而,它可能會找到多個符合條件的行。所以,它是查找和掃描的混合體,此類索引訪問只有當使用非惟一性索引或者惟一性索引的非惟一性前綴時纔會發生。把它叫作ref是由於索引要跟某個參考值相比較。這個參考值或者是一個常數,或者是來自多表查詢前一個表裏的結果值。

?
1
EXPLAIN select * from person where name = 'alex' ;

 

4.5  ref_or_null 該聯接類型如同ref相似,結果包含空行.

上面這五種狀況都是很理想的索引使用狀況

4.6  type=range

  索引範圍掃描,常見於 <,<=,>,>=,between,in等操做符。 

?
1
EXPLAIN select * from person where id BETWEEN 1 and 5;

4.7  type=index   

  該聯接類型與ALL相同都是掃描表,但index只對索引樹進行掃描,而ALL是是對數據表文件的掃描。這一般比ALL快,由於索引文件一般比數據文件小。(也就是說雖然all和Index都是讀全表,但index是從索引中讀取的,而all是從硬盤中讀的)主要優勢是避免了排序,由於索引是排好序的。

Extra列中看到「Using index」,說明mysql正在使用覆蓋索引,只掃描索引的數據。 

?
1
EXPLAIN select id, name from person;

4.8  type=ALL 

  對於每一個來自於先前的表的行組合,進行完整的表掃描。若是表是第一個沒標記const的表,這一般很差,而且一般在它狀況下差。一般能夠增長更多的索引而不要使用ALL,使得行能基於前面的表中的常數值或列值被檢索出。

?
1
EXPLAIN select * from person;

六.  possible_keys :

  該 possible_keys列表示MySQL能夠從中選擇查找表中的行的索引。若是此列是NULL,則沒有相關的索引。在這種狀況下,您能夠經過檢查WHERE 子句來檢查是否引用某些適合索引的列,從而提升查詢的性能。若是是這樣,請建立一個適當的索引並使用 EXPLAIN再次檢查查詢 。

另外若是這個列出現大量可能被使用的索引(例如多於3 個), 那麼這 意味着備選索引數量太多了,同時也可能提示存在無效的索引。

七. key :

 該key 列指出mysql優化器決定選擇使用哪一個索引來優化對該表的訪問。通常來講SQL查詢中的每一個表都只會使用一個索引。可是也存在索引合併的少數例外狀況,如給定表上用到了兩個或者更多索引。查詢過程當中由優化器來決定實際使用的索引。若是possible_keys索引列表中沒有適合查找行的索引,那麼這個key可能會命名一個不存在於該possible_keys值中的索引 。簡單且重要

八. key_len :

  該key_len 列定義了mysql在索引裏使用的字節數。若是mysql正在使用的只是索引裏的某些列,那麼就能夠用這個值來算出具體是哪些列。在mysql5.5及之前的版本里,只能使用索引的最左前綴。例如,sakila.film_actor的主鍵是兩個SMALLINT列,而且每一個SMALLINT列是兩個字節,那麼索引中的每項是4個字節。也即說明key_len經過查找表的定義而被計算出,而不是表中的數據。

在不損失精確性的狀況下,長度越短越好.

九. ref : 

  ref 列顯示使用哪一個列或常數與key一塊兒從表中選擇數據行。指出對 key 列所選擇的索引的查找方式,常見的值有 const, func, NULL, 具體字段名。當 key 列爲 NULL ,即不使用索引時 。若是值是func,則使用的值是某個函數的結果

複製代碼
create table a11(id int primary key, age int);
insert into a11 value(1, 10),(2, 10);

mysql> desc select * from a11 where age=10;
+----+-------------+-------+------+---------------+------+---------+------+------+-------------+
| id | select_type | table | type | possible_keys | key  | key_len | ref  | rows | Extra       |
+----+-------------+-------+------+---------------+------+---------+------+------+-------------+
|  1 | SIMPLE      | a11   | ALL  | NULL          | NULL | NULL    | NULL |    2 | Using where |
+----+-------------+-------+------+---------------+------+---------+------+------+-------------+
注意:當 key 列爲 NULL , ref 列也相應爲 NULL 。

mysql> desc select * from a11 where id=1;
+----+-------------+-------+-------+---------------+---------+---------+-------+------+-------+
| id | select_type | table | type  | possible_keys | key     | key_len | ref   | rows | Extra |
+----+-------------+-------+-------+---------------+---------+---------+-------+------+-------+
|  1 | SIMPLE      | a11   | const | PRIMARY       | PRIMARY | 4       | const |    1 |       |
+----+-------------+-------+-------+---------------+---------+---------+-------+------+-------+

注意:此次 key 列使用了主鍵索引,where id=11 爲常量, ref 列的 const 即是指這種常量。
複製代碼

 

十.row :

  這一列是mysql評估 爲了找到所需的行而要讀取的行數。這個數字是內嵌循環關聯計劃裏的循環數目,也就是說它不是mysql認爲它最終要從表裏讀取出來的行數,而是mysql爲了找到符合查詢的每一點上標準的那些行而必須讀取的行的平均數。

rows 列提供了試圖分析全部存在於累計結果集中的行數目的MySQL 優化器估計值。執行計劃很容易描述這個很困難的統計量。

查詢中總的讀操做數量是基於合併以前行的每一行的rows 值的連續積累而得出的。這是一種嵌套行算法。

簡單且重要,數值越大越很差,說明沒有用好索引

十一.Extra:

 該列包含 MySQL 查詢的詳細信息。

 10.1 Not exists : 不存在信息

 10.2 range checked for each record :沒有找到合適的索引

 10.3 Using index condition :出現這個說明mysql使用了覆蓋索引,避免訪問了表的數據行,效率不錯!

複製代碼
建表及插入數據:
create table a13 (id int primary key, age int);
insert into a13 value(1, 10),(2, 10);
mysql> explain select id from a13;
+----+-------------+-------+------------+-------+---------------+---------+---------+------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+------------+-------+---------------+---------+---------+------+------+----------+-------------+
| 1  | SIMPLE        | a13   | NULL         | index | NULL            | PRIMARY | 4   | NULL|  2   | Using index |
+----+-------------+-------+------------+-------+---------------+---------+---------+------+------+----------+-------------+
由於 id 爲主鍵索引,索引中直接包含了 id 的值,因此無需訪問表,直接查找索引就能返回結果。

mysql> explain select age from a13;
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------+
| 1  | SIMPLE        | a13   | NULL         | ALL  | NULL            | NULL |  NULL  | NULL|  2   |  NULL |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------+
age 列沒有索引,所以沒有 Using index ,意即須要訪問表。
爲 age 列添加索引:
create table a14 (id int primary key, age int);
insert into a14 value(1, 10),(2, 10);
create index age on a14(id, age);
mysql> explain select age from a14;
+----+-------------+-------+------------+-------+---------------+------+---------+------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows |  Extra |
+----+-------------+-------+------------+-------+---------------+------+---------+------+------+----------+-------------+
| 1  | SIMPLE      | a14   | NULL       | index|     NULL        | age |       9    | NULL| 2     |Using index |
+----+-------------+-------+------------+-------+---------------+------+---------+------+------+----------+-------------+
如今索引 age 中也包含了 age 列的值,所以不用訪問表便能返回結果了。
複製代碼

 10.4 using temporary :mysql對查詢結果進行排序的時候使用了一張臨時表。

複製代碼
mysql> EXPLAIN SELECT p.id,d.did from person p LEFT JOIN dept d ON p.dept_id = d.did group by p.dept_id ORDER BY p.dept_id;
+----+-------------+-------+--------+---------------+---------+---------+------------+------+---------------------------------+
| id | select_type | table | type   | possible_keys | key     | key_len | ref        | rows | Extra                           |
+----+-------------+-------+--------+---------------+---------+---------+------------+------+---------------------------------+
|  1 | SIMPLE      | p     | ALL    | NULL          | NULL    | NULL    | NULL       |    8 | Using temporary; Using filesort |
|  1 | SIMPLE      | d     | eq_ref | PRIMARY       | PRIMARY | 4       | test.p.dept_id| 1 | Using where; Using index        |

咱們發如今執行這條SQL語句時出現了 using temporary,咱們再來看看下面這條SQL語句,去掉 條件中 group by 分組

mysql> EXPLAIN SELECT p.id,d.did from person p LEFT JOIN dept d ON p.dept_id = d.did ORDER BY p.dept_id;
+----+-------------+-------+--------+---------------+---------+---------+------------+------+--------------------------+
| id | select_type | table | type   | possible_keys | key     | key_len | ref        | rows | Extra                    |
+----+-------------+-------+--------+---------------+---------+---------+------------+------+--------------------------+
|  1 | SIMPLE      | p     | ALL    | NULL          | NULL    | NULL    | NULL       |    8 | Using filesort           |
|  1 | SIMPLE      | d     | eq_ref | PRIMARY       | PRIMARY | 4       | test.p.dept_id|1  | Using where; Using index |
+----+-------------+-------+--------+---------------+---------+---------+------------+------+--------------------------+

而爲何第一個用了臨時表,而第二個沒有用呢?
由於若是有GROUP BY子句,或者若是GROUP BY中的字段都來自其餘的表而非鏈接順序中的第一個表的話,就會建立一個臨時表了。

那麼如何解決呢?
我們爲group by 字段添加一個索引 

mysql> alter table person add index did_idx(dept_id);
Query OK, 0 rows affected

mysql> EXPLAIN SELECT p.id,d.did from person p LEFT JOIN dept d ON p.dept_id = d.did group by p.dept_id ORDER BY p.dept_id;
+----+-------------+-------+--------+---------------+---------+---------+------------+------+--------------------------+
| id | select_type | table | type   | possible_keys | key     | key_len | ref        | rows | Extra                    |
+----+-------------+-------+--------+---------------+---------+---------+------------+------+--------------------------+
|  1 | SIMPLE      | p     | index  | NULL          | did_idx | 5       | NULL       |    8 | Using index              |
|  1 | SIMPLE      | d     | eq_ref | PRIMARY       | PRIMARY | 4       | test.p.dept_id| 1 | Using where; Using index |
+----+-------------+-------+--------+---------------+---------+---------+------------+------+--------------------------+

爲何添加個索引就不會建立臨時表了呢? 緣由就在於 SQL查詢時優先在索引樹中執行,若是索引樹知足不了當前SQL,纔會進行數據表查詢,那麼如今加了索引,
已經能夠知足查詢條件了,就沒有必要建立臨時表了
複製代碼

 10.5 using filesort: mysql對數據不是按照表內的索引順序進行讀取,而是使用了其餘字段從新排序.

複製代碼
mysql> EXPLAIN select * from person ORDER BY id;
+----+-------------+--------+-------+---------------+---------+---------+------+------+-------+
| id | select_type | table  | type  | possible_keys | key     | key_len | ref  | rows | Extra |
+----+-------------+--------+-------+---------------+---------+---------+------+------+-------+
|  1 | SIMPLE      | person | index | NULL          | PRIMARY | 4       | NULL |    8 |       |
+----+-------------+--------+-------+---------------+---------+---------+------+------+-------+
若是咱們用聚合主鍵進行排序,則Extra 爲null,咱們知道在innodb引擎中,主鍵爲聚合索引,插入數據就會排好順序.最後說明mysql是按照表內的索引順序進行讀的

再看下面的列子:
mysql> EXPLAIN select * from person ORDER BY salary;
+----+-------------+--------+------+---------------+------+---------+------+------+----------------+
| id | select_type | table  | type | possible_keys | key  | key_len | ref  | rows | Extra          |
+----+-------------+--------+------+---------------+------+---------+------+------+----------------+
|  1 | SIMPLE      | person | ALL  | NULL          | NULL | NULL    | NULL |    8 | Using filesort |
+----+-------------+--------+------+---------------+------+---------+------+------+----------------+
咱們使用非主鍵字段進行排序,這是mysql就不能按照表內的索引順序進行讀了.須要讀取數據行後再進行排序處理
複製代碼

 10.6 using where: 表示 MySQL 服務器從存儲引擎收到查詢數據,再進行「後過濾」(Post-filter)。所謂「後過濾」,就是先讀取整行數據,再檢查此行是否符合 where 句的條件,符合就留下,不符合便丟棄。由於檢查是在讀取行後才進行的,因此稱爲「後過濾」。

複製代碼
建表及插入數據:
create table a16 (num_a int not null, num_b int not null, key(num_a));
insert into a16 value(1,1),(1,2),(2,1),(2,2);
mysql> explain select * from a16 where num_a=1;
+----+-------------+-------+------------+------+---------------+-------+---------+-------+------+----------+-------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+------------+------+---------------+-------+---------+-------+------+----------+-------+
| 1  | SIMPLE      | a16   | NULL       | ref  | num_a         | num_a | 4        | const| 2     |  NULL |
+----+-------------+-------+------------+------+---------------+-------+---------+-------+------+----------+-------+

雖然查詢中有 where 子句,但只有 num_a=1 一個條件,且 num_a 列存在索引,經過索引便能肯定返回的行,無需進行「後過濾」。
因此,並不是帶 WHERE 子句就會顯示"Using where"的。
mysql> explain select * from a16 where num_a=1 and num_b=1;
+----+-------------+-------+------------+------+---------------+-------+---------+-------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+------------+------+---------------+-------+---------+-------+------+----------+-------------+
| 1  | SIMPLE      | a16   | NULL       | ref  | num_a            | num_a | 4     | const | 2  | Using where |
+----+-------------+-------+------------+------+---------------+-------+---------+-------+------+----------+-------------+

此查詢增長了條件 num_b=1 ,此列沒有索引,但能夠看到查詢一樣能使用 num_a 索引。 MySQL 先經過索引 num_a 找到 num_a=1 的行,而後讀取整行數據,
再檢查 num_b 是否等於 1 ,執行過程看上去象這樣:

num_a索引|num_b 沒有索引,屬於行數據
+-------+-------+
| num_a | num_b | where 子句(num_b=1)
+-------+-------+
| 1 | 1 | 符合
| 1 | 2 | 不符合
| ... | ... | ...
+-------+-------+
複製代碼

 

詳情參考官方文檔: https://dev.mysql.com/doc/refman/5.6/en/using-explain.html

十二. EXPLAIN結果中哪些信息要引發關注

們使用EXPLAIN解析SQL執行計劃時,若是有下面幾種狀況,就須要特別關注下了:

  首先看下 type 這列的結果,若是有類型是 ALL 時,表示預計會進行全表掃描(full table scan)。一般全表掃描的代價是比較大的,建議建立適當的索引,經過索引檢索避免全表掃描。

  再來看下 Extra 列的結果,若是有出現 Using temporary 或者 Using filesort 則要多加關注:

  Using temporary,表示須要建立臨時表以知足需求,一般是由於GROUP BY的列沒有索引,或者GROUP BY和ORDER BY的列不同,也須要建立臨時表,建議添加適當的索引。

  Using filesort,表示沒法利用索引完成排序,也有多是由於多表鏈接時,排序字段不是驅動表中的字段,所以也沒辦法利用索引完成排序,建議添加適當的索引。

  Using where,一般是由於全表掃描或全索引掃描時(type 列顯示爲 ALL 或 index),又加上了WHERE條件,建議添加適當的索引。

其餘狀態例如:Using index、Using index condition、Using index for group-by 則都還好,不用緊張。

相關文章
相關標籤/搜索