1. 程式人生 > >分析診斷工具之二:Explain結果解讀與實踐

分析診斷工具之二:Explain結果解讀與實踐

  MySQL的EXPLAIN命令用於SQL語句的查詢執行計劃(QEP)。這條命令的輸出結果能夠讓我們瞭解MySQL 優化器是如何執行SQL 語句的。這條命令並沒有提供任何調整建議,但它能夠提供重要的資訊幫助你做出調優決策。

語法

MySQL 的EXPLAIN 語法可以執行在SELECT 語句或者特定表上。如果作用在表上,那麼此命令等同於DESC 表命令。UPDATE和DELETE 命令也需要進行效能改進,當這些命令不是直接在表的主碼上執行時,為了確保最優化的索引使用率,需要把它們改寫成SELECT 語句(以便對它們執行EXPLAIN 命令)。請看下面的示例:

UPDATE table1  
SET col1 = X, col2 = Y  
WHERE id1 = 9  
AND dt >= '2010-01-01';

這個UPDATE語句可以被重寫成為下面這樣的SELECT語句:

SELECT col1, col2  
FROM table1  
WHERE id1 = 9  
AND dt >= '2010-01-01';

在5.6.10以後的版本里面,是可以直接對dml語句進行explain分析操作的.MySQL 優化器是基於開銷來工作的,它並不提供任何的QEP的位置。這意味著QEP 是在每條SQL 語句執行的時候動態地計算出來的。在MySQL 儲存過程中的SQL 語句也是在每次執行時計算QEP 的。儲存過程快取僅僅解析查詢樹。

DESC命令: 

查看錶資訊:

desc檢視執行計劃資訊:

Explain 結果解讀與實踐

注:單獨一行的"%%"及"`"表示分隔內容,就象分開“第一章”“第二章”。

explain 可以分析 select 語句的執行,即 MySQL 的“執行計劃”:

mysql> explain select 1;
+----+-------------+-------+------+---------------+------+---------+------+------+----------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+------+---------------+------+---------+------+------+----------------+
| 1 | SIMPLE | NULL | NULL | NULL | NULL | NULL | NULL | NULL | No tables used |
+----+-------------+-------+------+---------------+------+---------+------+------+----------------+

用"\G"代替分號可得到豎排的格式:
mysql> explain select 1\G
*************************** 1
id: 1
select_type: SIMPLE
table: NULL
type: NULL
possible_keys: NULL
key: NULL
key_len: NULL
ref: NULL
rows: NULL
Extra: No tables used

可以用 desc 代替 explain :desc select 1;
mysql> desc select 1;
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+----------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+----------------+
| 1 | SIMPLE | NULL | NULL | NULL | NULL | NULL | NULL | NULL | NULL | NULL | No tables used |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+----------------+
1 row in set, 1 warning (0.00 sec)


二、結果列詳細說明:

2.1、id 列

用下面的例子來說明,在多張表的查詢中,突出的更顯著。
1、建表:
create table a(a_id int);
create table b(b_id int);
create table c(c_id int);
2、執行查詢explain select * from a join b on a_id=b_id where b_id in (select c_id from c);
mysql> explain select * from a join b on a_id=b_id where b_id in (select c_id from c);
+----+--------------------+-------+...
| id | select_type | table |...
+----+--------------------+-------+...
| 1 | PRIMARY | a |...
| 1 | PRIMARY | b |...
| 2 | DEPENDENT SUBQUERY | c |...
+----+--------------------+-------+...
從 3 個表中查詢,對應輸出 3 行,每行對應一個表, id 列表示執行順序,id 越大,越先執行,id 相同,由上至下執行。此處的執行順序為(以 table 列表示):c -> a -> b

2.2、select_type 列

select_type 列提供了各種表示table 列引用的使用方式的型別。最常見的值包括SIMPLE、PRIMARY、DERIVED 和UNION。其他可能的值還有UNION RESULTDEPENDENT SUBQUERY、DEPENDENT UNION、UNCACHEABLE UNION 以及UNCACHEABLE QUERY

1. SIMPLE

對於不包含子查詢和其他複雜語法的簡單查詢,這是一個常 見的型別。

2. PRIMARY

這是為更復雜的查詢而建立的首要表(也就是最外層的表)。這個型別通常可以在DERIVED 和UNION 型別混合使用時見到。

3. DERIVED

當一個表不是一個物理表時,那麼就被叫做DERIVED。下面的SQL 語句給出了一個QEP 中DERIVED select-type 型別的

示例:

mysql> EXPLAIN SELECT MAX(id)
-> FROM (SELECT id FROM users WHERE first = ‘west’) c;

4. DEPENDENT SUBQUERY

這個select-type 值是為使用子查詢而定義的。下面的SQL語句提供了這個值:

mysql> EXPLAIN SELECT p.*
-> FROM parent p
-> WHERE p.id NOT IN (SELECT c.parent_id FROM child c);

5. UNION

這是UNION 語句其中的一個SQL 元素。

6. UNION RESULT

這是一系列定義在UNION 語句中的表的返回結果。當select_type 為這個值時,經常可以看到table 的值是<unionN,M>,這說明匹配的id 行是這個集合的一部分。下面的SQL產生了一個UNION和UNION RESULT select-type:

mysql> EXPLAIN SELECT p.* FROM parent p WHERE p.val
LIKE ‘a%-> UNION
-> SELECT p.* FROM parent p WHERE p.id > 5;

2.3、table 列

table 列是EXPLAIN 命令輸出結果中的一個單獨行的唯一識別符號。這個值可能是表名、表的別名或者一個為查詢產生臨時表的識別符號,如派生表、子查詢或集合。

2.4、type 列

type 列代表QEP 中指定的表使用的連線方式。下面是最常用的幾種連線方式:

先從最佳型別到最差型別:NULL>system>const>eq_ref>ref>range>index>All  重要且困難

2.4.0、NULL
在優化過程中就已得到結果,不用再訪問表或索引。

mysql> explain select min(id) from a9;
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+------------------------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+------------------------------+
| 1 | SIMPLE | NULL | NULL | NULL | NULL | NULL | NULL | NULL | NULL | NULL | Select tables optimized away |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+------------------------------+

2.4.1、system

表僅有一行,這是const型別的特列,平時少見。

建表及插入資料:

create table a9(id int primary key);
insert into a9 value(1);

mysql> explain select * from a9;
+----+-------------+-------+------------+-------+---------------+---------+---------+------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+-------+---------------+---------+---------+------+------+----------+-------------+
| 1 | SIMPLE | a9 | NULL | index | NULL | PRIMARY | 4 | NULL | 1 | 100.00 | Using index |
+----+-------------+-------+------------+-------+---------------+---------+---------+------+------+----------+-------------+

2.4.2、const 被稱為“常量”,這個詞不好理解,不過出現 const 的話就表示發生下面兩種情況:

  • 在整個查詢過程中這個表最多隻會有一條匹配的行,比如主鍵 id=1 就肯定只有一行;
  • 只需讀取一次表資料便能取得所需的結果,且表資料在分解執行計劃時讀取。

表最多有一個匹配行,const用於比較primary key 或者unique索引。因為只匹配一行資料,所以很快記住一定是用到primary key 或者unique,並且只檢索出兩條資料的 情況下才會是const,看下面這條語句

explain SELECT * FROM `asj_admin_log` limit 1

,結果是

雖然只搜尋一條資料,但是因為沒有用到指定的索引,所以不會使用const.繼續看下面這個

explain SELECT * FROM `asj_admin_log` where log_id = 111

log_id是主鍵,所以使用了const。所以說可以理解為const是最優化的

示例:

建表及插入資料:

create table a7(id int primary key, c1 char(20) not null, c2 text not null, c3 text not null);
insert into a7 values(1, 'asdfasdf', 'asdfasdf', 'asdfasdf'), (2, 'asdfasdf', 'asdfasdf', 'asdfasdf');

mysql> explain extended select * from a7 where id=1;
+----+-------------+-------+------------+-------+---------------+---------+---------+-------+------+----------+-------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+-------+---------------+---------+---------+-------+------+----------+-------+
| 1 | SIMPLE | a7 | NULL | const | PRIMARY | PRIMARY | 4 | const | 1 | 100.00 | NULL |
+----+-------------+-------+------------+-------+---------------+---------+---------+-------+------+----------+-------+

用 show warnings 檢視 MySQL 是如何優化的:

mysql> show warnings\G
Message: select '1' AS `id`,'asdfasdf' AS `c1`,'asdfasdf' AS `c2`,'asdfasdf' AS
`c3` from `test`.`a` where 1

查詢返回的結果為:
mysql> select * from a7 where id=1;
+----+----------+----------+----------+
| id | c1 | c2 | c3 |
+----+----------+----------+----------+
| 1 | asdfasdf | asdfasdf | asdfasdf |
+----+----------+----------+----------+

可以看出,返回結果中的欄位值都以“值 AS 欄位名”的形式直接出現在優化後的 select 語句中。

修改一下查詢:
mysql> explain select * from a7 where id in(1,2);
+----+-------------+-------+------------+-------+---------------+---------+---------+------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+-------+---------------+---------+---------+------+------+----------+-------------+
| 1 | SIMPLE | a7 | NULL | range | PRIMARY | PRIMARY | 4 | NULL | 2 | 100.00 | Using where |
+----+-------------+-------+------------+-------+---------------+---------+---------+------+------+----------+-------------+
當返回結果超過 1 條時, type 便不再為 const 了。

重新建表及插入資料:
create table a8 (id int not null);
insert into a8 value(1),(2),(3);

mysql> explain select * from a8 where id=1;
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+
| 1 | SIMPLE | a8 | NULL | ALL | NULL | NULL | NULL | NULL | 3 | 33.33 | Using where |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+
目前表中只有一條 id=1 的記錄,但 type 已為 ALL ,因為只有唯一性索引才能保證表中最多隻有一條記錄,只有這樣 type 才有可能為 const 。
為 id 加普通索引後, type 變為 ref ,改為加唯一或主鍵索引後, type 便變為 const 了。

2.4.3、eq_ref 使用有唯一性索引查詢(主鍵或唯一性索引)

使用這種索引查詢,mysql知道最多隻返回一條符合條件的記錄,這種訪問方法可以在mysql使用主鍵或者唯一性索引查詢時看到,它會將它們與某個參考值做比較。mysql對於這類訪問型別的優化做得非常好,因為它知道無需估計匹配行的範圍或在找到匹配行後再繼續查詢。

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

建表及插入資料:
create table a5(id int primary key);
create table a5_info(id int primary key, title char(1));
insert into a5 value(1),(2);
insert into a5_info value(1, 'a'),(2, 'b');
mysql> explain select * from a5 join a5_info using(id);
+----+-------------+---------+------------+-------+---------------+---------+---------+------+------+----------+----------------------------------------------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+---------+------------+-------+---------------+---------+---------+------+------+----------+----------------------------------------------------+
| 1 | SIMPLE | a5 | NULL | index | PRIMARY | PRIMARY | 4 | NULL | 2 | 100.00 | Using index |
| 1 | SIMPLE | a5_info | NULL | ALL | PRIMARY | NULL | NULL | NULL | 2 | 50.00 | Using where; Using join buffer (Block Nested Loop) |
+----+-------------+---------+------------+-------+---------------+---------+---------+------+------+----------+----------------------------------------------------+
此時 a5_info 每條記錄與 a5 一一對應,通過主鍵 id 關聯起來,所以 a5_info 的 type 為 eq_ref。

刪除 a_info 的主鍵:ALTER TABLE `a5_info` DROP PRIMARY KEY;
現在 a_info 已經沒有索引了:

mysql> explain select * from a5 join a5_info using(id);
+----+-------------+---------+------------+--------+---------------+---------+---------+--------------------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+---------+------------+--------+---------------+---------+---------+--------------------+------+----------+-------------+
| 1 | SIMPLE | a5_info | NULL | ALL | NULL | NULL | NULL | NULL | 2 | 100.00 | NULL |
| 1 | SIMPLE | a5 | NULL | eq_ref | PRIMARY | PRIMARY | 4 | ud_omcs.a5_info.id | 1 | 100.00 | Using index |
+----+-------------+---------+------------+--------+---------------+---------+---------+--------------------+------+----------+-------------+
這次 MySQL 調整了執行順序,先全表掃描 a5_info 表,再對錶 a5 進行 eq_ref 查詢,因為 a5 表 id 還是主鍵。

刪除 a 的主鍵:alter table a5 drop primary key;
現在 a 也沒有索引了:

mysql> explain select * from a5 join a5_info using(id);
+----+-------------+---------+------------+------+---------------+------+---------+------+------+----------+----------------------------------------------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+---------+------------+------+---------------+------+---------+------+------+----------+----------------------------------------------------+
| 1 | SIMPLE | a5 | NULL | ALL | NULL | NULL | NULL | NULL | 2 | 100.00 | NULL |
| 1 | SIMPLE | a5_info | NULL | ALL | NULL | NULL | NULL | NULL | 2 | 50.00 | Using where; Using join buffer (Block Nested Loop) |
+----+-------------+---------+------------+------+---------------+------+---------+------+------+----------+----------------------------------------------------+

現在兩個表都使用全表掃描了。

建表及插入資料:
create table a6(id int primary key);
create table a6_info(id int, title char(1), key(id));
insert into a6 value(1),(2);
insert into a6_info value(1, 'a'),(2, 'b');

現在 a6_info 表 id 列變為普通索引(非唯一性索引):

mysql> explain select * from a6 join a6_info using(id) where a6.id=1;
+----+-------------+---------+------------+-------+---------------+---------+---------+-------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+---------+------------+-------+---------------+---------+---------+-------+------+----------+-------------+
| 1 | SIMPLE | a6 | NULL | const | PRIMARY | PRIMARY | 4 | const | 1 | 100.00 | Using index |
| 1 | SIMPLE | a6_info | NULL | ref | id | id | 5 | const | 1 | 100.00 | NULL |
+----+-------------+---------+------------+-------+---------------+---------+---------+-------+------+----------+-------------+

a6_info 表 type 變為 ref 型別了。

所以,唯一性索引才會出現 eq_ref (非唯一性索引會出現 ref ),因為唯一,所以最多隻返回一條記錄,找到後無需繼續查詢,因此比 ref 更快。

2.4.4、 ref 非唯一性索引訪問

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

ref_or_null是ref之上的一個變體,它意味著mysql必須在初次查詢的結果裡進行第二次查詢以找出null條目。

   對於每個來自於前面的表的行組合,所有有匹配索引值的行將從這張表中讀取。如果聯接只使用鍵的最左邊的字首,或如果鍵不是UNIQUE或PRIMARY KEY(換句話說,如果聯接不能基於關鍵字選擇單個行的話),則使用ref。如果使用的鍵僅僅匹配少量行,該聯接型別是不錯的。

看下面這條語句 explain select * from uchome_space where uchome_space.friendnum = 0,得到結果如下,這條語句能搜出1w條資料

示例2

建表:
create table a4(a_id int not null, key(a_id));
insert into a4 values(1),(2),(3),(4),(5),(6),(7),(8),(9),(10);
mysql> explain select * from a4 where a_id=1;
+----+-------------+-------+------------+------+---------------+------+---------+-------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+------+---------------+------+---------+-------+------+----------+-------------+
| 1 | SIMPLE | a4 | NULL | ref | a_id | a_id | 4 | const | 1 | 100.00 | Using index |
+----+-------------+-------+------------+------+---------------+------+---------+-------+------+----------+-------------+

2.4.5、 ref_or_null 

  該聯接型別如同ref,但是添加了MySQL可以專門搜尋包含NULL值的行。在解決子查詢中經常使用該聯接型別的優化。

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

2.4.6、 index_merge

  該聯接型別表示使用了索引合併優化方法。在這種情況下,key列包含了使用的索引的清單,key_len包含了使用的索引的最長的關鍵元素。

2.4.7、 unique_subquery 

2.4.8、 index_subquery

2.4.9、 range 給定範圍內的檢索,使用一個索引來檢查行。

使用between或者在where自己裡帶有>的查詢。

還有in()或者or列表,也會顯示為範圍掃描,然而這兩者其實是不同型別的訪問,在效能上也有差異(結論:or的效率為O(n),而in的效率為O(logn),如果in和or所在列有索引或者主鍵的話,or和in沒啥差別,執行計劃和執行時間都幾乎一樣。如果in和or所在列沒有索引的話in效率高。)。此類掃描的開銷跟索引型別的相當。)。

舉例說明:
建表:
create table aaa(a_id int not null, key(a_id));
insert into aaa values(1),(2),(3),(4),(5),(6),(7),(8),(9),(10);

mysql> explain select * from aaa where a_id > 1;
+----+-------------+-------+------------+-------+---------------+------+---------+------+------+----------+--------------------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+-------+---------------+------+---------+------+------+----------+--------------------------+
| 1 | SIMPLE | aaa | NULL | range | a_id | a_id | 4 | NULL | 9 | 100.00 | Using where; Using index |
+----+-------------+-------+------------+-------+---------------+------+---------+------+------+----------+--------------------------+

IN 比較符也會用 range 表示
mysql> explain select * from aaa where a_id in (1,3,4);
+----+-------------+-------+------------+-------+---------------+------+---------+------+------+----------+--------------------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+-------+---------------+------+---------+------+------+----------+--------------------------+
| 1 | SIMPLE | aaa | NULL | range | a_id | a_id | 4 | NULL | 3 | 100.00 | Using where; Using index |
+----+-------------+-------+------------+-------+---------------+------+---------+------+------+----------+--------------------------+

2.4.10、 index     該聯接型別與ALL相同都是掃描表,但index只對索引樹進行掃描,而ALL是是對資料表文件的掃描。這通常比ALL快,因為索引檔案通常比資料檔案小。(也就是說雖然all和Index都是讀全表,但index是從索引中讀取的,而all是從硬碟中讀的)主要優點是避免了排序,因為索引是排好序的。

Extra列中看到“Using index”,說明mysql正在使用覆蓋索引,只掃描索引的資料。

舉例說明:
1、建表:
create table aa(a_id int not null, key(a_id));
insert into aa value(1),(2);
2、查詢
mysql> explain select * from aa;
+----+-------------+-------+------------+-------+---------------+------+---------+------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+-------+---------------+------+---------+------+------+----------+-------------+
| 1 | SIMPLE | aa | NULL | index | NULL | a_id | 4 | NULL | 2 | 100.00 | Using index |
+----+-------------+-------+------------+-------+---------------+------+---------+------+------+----------+-------------+

2.4.11、  ALL 
全表掃描,MySQL 從頭到尾掃描整張表查詢行。

mysql> explain select * from a;
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------+
| 1 | SIMPLE | a | NULL | ALL | NULL | NULL | NULL | NULL | 4 | 100.00 | NULL |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------+

如果加上 limit 如 select * from a limit 10 MySQL 會掃描 10 行,但掃描方式不會變,還是從頭到尾掃描。

例如,在查詢裡使用了Limit,或者在Extra列中顯示“Using distinct/not exists”

2.5、possible_keys 列     可能被用到的索引

一個會列出大量可能的索引(例如多於3 個)的QEP 意味著備選索引數量太多了,同時也可能提示存在一個無效的單列索引。

可以用第2 章詳細介紹過的SHOW INDEXES 命令來檢查索引是否有效且是否具有合適的基數。《SHOW INDEX語法的實際應用

為查詢確定QEP 的速度也會影響到查詢的效能。如果發現有大量的可能的索引,則意味著這些索引沒有被使用到。

相關的QEP 列還包括key 列。

示例:

建表:
create table a10 (a_id int primary key, a_age int, key (a_id, a_age));
insert into a10 values(1,2),(2,3);
mysql> select * from a10;
+------+-------+
| a_id | a_age |
+------+-------+
| 1 | 2 |
| 2 | 3 |
+------+-------+
此表有 主鍵及普通索引 兩個索引。
mysql> explain select * from a10 where a_id=2 and a_age=2;
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-----------------------------------------------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-----------------------------------------------------+
| 1 | SIMPLE | NULL | NULL | NULL | NULL | NULL | NULL | NULL | NULL | NULL | Impossible WHERE noticed after reading const tables |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-----------------------------------------------------+
1 row in set, 1 warning (0.00 sec)

mysql> explain select * from a10 where a_id=2 and a_age=3;
+----+-------------+-------+------------+-------+---------------+---------+---------+-------+------+----------+-------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+-------+---------------+---------+---------+-------+------+----------+-------+
| 1 | SIMPLE | a10 | NULL | const | PRIMARY,a_id | PRIMARY | 4 | const | 1 | 100.00 | NULL |
+----+-------------+-------+------------+-------+---------------+---------+---------+-------+------+----------+-------+
1 row in set, 1 warning (0.00 sec)

2.6、key 列 

  key 列指出mysql優化器決定選擇使用的索引來優化對該表的訪問。一般來說SQL 查詢中的每個表都僅使用一個索引。也存在索引合併的少數例外情況,如給定表上用到了兩個或者更多索引。查詢過程中實際使用的索引。

mysql> explain select * from a10 where a_id=2 and a_age=3;
+----+-------------+-------+------------+-------+---------------+---------+---------+-------+------+----------+-------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+-------+---------------+---------+---------+-------+------+----------+-------+
| 1 | SIMPLE | a10 | NULL | const | PRIMARY,a_id | PRIMARY | 4 | const | 1 | 100.00 | NULL |
+----+-------------+-------+------------+-------+---------------+---------+---------+-------+------+----------+-------+
1 row in set, 1 warning (0.00 sec)

2.7、key_len 列

  key_len 列定義了mysql在索引裡使用的位元組數。如果mysql正在使用的只是索引裡的某些列,那麼就可以用這個值來算出具體是哪些列。

在mysql5.5及以前的版本里,只能使用索引的最左字首。例如,sakila.film_actor的主鍵是兩個SMALLINT列,並且每個SMALLINT列是兩個位元組,那麼索引中的每項是4個位元組。也即說明key_len通過查詢表的定義而被計算出,而不是表中的資料

用於SQL 語句的連線條件的鍵的長度。此列值對於確認索引的有效性以及多列索引中用到的列的數目很重要。索引欄位最大可能使用的長度。

mysql> explain select * from a10 where a_id=2 and a_age=3;
+----+-------------+-------+------------+-------+---------------+---------+---------+-------+------+----------+-------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+-------+---------------+---------+---------+-------+------+----------+-------+
| 1 | SIMPLE | a10 | NULL | const | PRIMARY,a_id | PRIMARY | 4 | const | 1 | 100.00 | NULL |
+----+-------------+-------+------------+-------+---------------+---------+---------+-------+------+----------+-------+
1 row in set, 1 warning (0.00 sec)

a_id 是 int 型別,int 的長度是 4 位元組,所以 key_len 為 4。

示例2:

此列的一些示例值如下所示:

key_len: 4 // INT NOT NULL
key_len: 5 // INT NULL
key_len: 30 // CHAR(30) NOT NULL
key_len: 32 // VARCHAR(30) NOT NULL
key_len: 92 // VARCHAR(30) NULL CHARSET=utf8

從這些示例中可以看出,是否可以為空、可變長度的列以及key_len 列的值只和用在連線WHERE 條件中索引的列有關。索引中的其他列會在ORDER BY或者GROUP BY 語句中被用到。下面這個來自於著名的開源部落格軟體WordPress 的表展示瞭如何以最佳方式使用帶有定義好的表索引的SQL 語句:

CREATE TABLE `wp_posts` (  
`ID` bigint(20) unsigned NOT NULL AUTO_INCREMENT,  
`post_date` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',  
`post_status` varchar(20) NOT NULL DEFAULT 'publish' ,  
`post_type` varchar(20) NOT NULL DEFAULT 'post',  
PRIMARY KEY (`ID`),  
KEY `type_status_date`(`post_type`,`post_status`,`post_date`,`ID`)  
) DEFAULT CHARSET=utf8;

這個表的索引包括post_type、post_status、post_date 以及ID列。下面是一個演示索引列用法的SQL 查詢:

EXPLAIN SELECT ID, post_title FROM wp_posts WHERE post_type=’post’ AND post_date >2010-06-01’;

這個查詢的QEP 返回的key_len 是62。這說明只有post_type列上的索引用到了(因為(20×3)+2=62)。儘管查詢在WHERE 語句中使用了post_type 和post_date 列,但只有post_type 部分被用到了。其他索引沒有被使用的原因是MySQL 只能使用定義索引的最左邊部分。為了更好地利用這個索引,可以修改這個查詢來調整索引的列。請看下面的示例:

mysql> EXPLAIN SELECT ID, post_title
-> FROM wp_posts  
-> WHERE post_type='post'  
-> AND post_status='publish'  
-> AND post_date > '2010-06-01';

在SELECT查詢的新增一個post_status 列的限制條件後,QEP顯示key_len 的值為132,這意味著post_type、post_status、post_date三列(62+62+8,(20×3)+2,(20×3)+2,8)都被用到了。此外,這個索引的主碼列ID 的定義是使用MyISAM 儲存索引的遺留痕跡。當使用InnoDB 儲存引擎時,在非主碼索引中包含主碼列是多餘的,這可以從key_len 的用法看出來。

相關的QEP 列還包括帶有Using index 值的Extra 列。

2.8、ref 列

  顯示了之前的表在key列記錄的索引中查詢所用的列或常量。

指出對 key 列所選擇的索引的查詢方式,常見的值有 const, func, NULL, 具體欄位名。當 key 列為 NULL ,即不使用索引時,此值也相應的為 NULL 。

建表及插入資料:

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 | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+
| 1 | SIMPLE | a11 | NULL | ALL | NULL | NULL | NULL | NULL | 2 | 50.00 | Using where |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+

當 key 列為 NULL , ref 列也相應為 NULL 。
mysql> desc select * from a11 where id=1;
+----+-------------+-------+------------+-------+---------------+---------+---------+-------+------+----------+-------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+-------+---------------+---------+---------+-------+------+----------+-------+
| 1 | SIMPLE | a11 | NULL | const | PRIMARY | PRIMARY | 4 | const | 1 | 100.00 | NULL |
+----+-------------+-------+------------+-------+---------------+---------+---------+-------+------+----------+-------+
這次 key 列使用了主鍵索引,where id=1 中 1 為常量, ref 列的 const 便是指這種常量

mysql> desc select * from a11 where id in(1,2);
+----+-------------+-------+------------+-------+---------------+---------+---------+------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+-------+---------------+---------+---------+------+------+----------+-------------+
| 1 | SIMPLE | a11 | NULL | range | PRIMARY | PRIMARY | 4 | NULL | 2 | 100.00 | Using where |
+----+-------------+-------+------------+-------+---------------+---------+---------+------+------+----------+-------------+
不理解 ref 為 NULL 的含意,比如上面這個查詢, key 列有使用索引,但 ref 列卻為 NULL 。網上搜索及查閱了一下 MySQL 幫助手冊都沒有找到相關的描述。

再建表及插入資料:
create table a12(id int primary key, a_name int not null);
create table b12(id int primary key, b_name int not null);
insert into a12 value(1, 1),(2, 2),(3, 3);
insert into b12 value(1, 111),(2, 222),(3, 333);

mysql> explain select * from a12 join b12 using(id);
...+-------+--------+...+---------+...+-----------+...
...| table | type |...| key |...| ref |...
...+-------+--------+...+---------+...+-----------+...
...| a | ALL |...| NULL |...| NULL |...
...| b | eq_ref |...| PRIMARY |...| test.a.id |...
...+-------+--------+...+---------+...+-----------+...

這裡 test.a.id 即為具體欄位,意為根據表 a 的 id 欄位的值查詢表 b 的主鍵索引。
mysql> explain select * from a12 join b12 using(id) where b12.id=3;
+----+-------------+-------+------------+-------+---------------+---------+---------+-------+------+----------+-------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+-------+---------------+---------+---------+-------+------+----------+-------+
| 1 | SIMPLE | a12 | NULL | const | PRIMARY | PRIMARY | 4 | const | 1 | 100.00 | NULL |
| 1 | SIMPLE | b12 | NULL | const | PRIMARY | PRIMARY | 4 | const | 1 | 100.00 | NULL |
+----+-------------+-------+------------+-------+---------------+---------+---------+-------+------+----------+-------+
因為 a join b 的條件為 id 相等,而 b.id=1 ,就是 a.id 也為 1 ,所以 a,b 兩個表的 ref 列都為 const 。

ref 為 func 的情況出現在子查詢中,暫不明其原理:
mysql> explain select * from a12 where id in (select id from b12 where id in (1,2));
+----+-------------+-------+------------+--------+---------------+---------+---------+----------------+------+----------+--------------------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+--------+---------------+---------+---------+----------------+------+----------+--------------------------+
| 1 | SIMPLE | b12 | NULL | index | PRIMARY | PRIMARY | 4 | NULL | 3 | 66.67 | Using where; Using index |
| 1 | SIMPLE | a12 | NULL | eq_ref | PRIMARY | PRIMARY | 4 | ud_omcs.b12.id | 1 | 100.00 | NULL |
+----+-------------+-------+------------+--------+---------------+---------+---------+----------------+------+----------+--------------------------+

2.9、rows 列

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

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

查詢中總的讀運算元量是基於合併之前行的每一行的rows 值的連續積累而得出的。這是一種巢狀行演算法。

以連線兩個表的QEP 為例。通過id=1 這個條件找到的第一行的rows 值為1,這等於對第一個表做了一次讀操作。第二行是通過id=2 找到的,rows 的值為5。這等於有5 次讀操作符合當前1 的積累量。參考兩個表,讀操作的總數目是6。在另一個QEP中,第一rows 的值是5,第二rows 的值是1。這等於第一個表有5 次讀操作,對5個積累量中每個都有一個讀操作。因此兩個表總的讀操作的次數是10(5+5)次。

最好的估計值是1,一般來說這種情況發生在當尋找的行在表中可以通過主鍵或者唯一鍵找到的時候。

在下面的QEP 中,外面的巢狀迴圈可以通過id=1 來找到,其估計的物理行數是1。第二個迴圈處理了10行。

********************* 1. row ***********************  
id: 1  
select_type: SIMPLE  
table: p  
type: const  
possible_keys: PRIMARY  
key: PRIMARY  
key_len: 4  
ref: const  
rows: 1  
Extra:  
********************* 2. row ***********************  
id: 1  
select_type: SIMPLE  
table: c  
type: ref  
possible_keys: parent_id  
key: parent_id  
key_len: 4  
ref: const  
rows: 10  
Extra:

可以使用SHOW STATUS 命令來檢視實際的行操作。這個命令可以提供最佳的確認物理行操作的方式。請看下面的示例:

mysql> SHOW SESSION STATUS LIKE 'Handler_read%';  
+-----------------------+-------+  
| Variable_name         | Value |  
+-----------------------+-------+  
| Handler_read_first    | 0     |  
| Handler_read_key      | 0     |   
| Handler_read_last     | 0     |  
| Handler_read_next     | 0     |  
| Handler_read_prev     | 0     |  
| Handler_read_rnd      | 0     |  
| Handler_read_rnd_next | 11    |  
+-----------------------+-------+  
7 rows in set (0.00 sec)

在下一個QEP 中,通過id=1 找到的外層巢狀迴圈估計有160行。第二個迴圈估計有1 行。

********************* 1. row ***********************  
id: 1  
select_type: SIMPLE  
table: p  
type: ALL  
possible_keys: NULL  
key: NULL  
key_len: NULL  
ref: NULL  
rows: 160  
Extra:  

********************* 2. row ***********************  
id: 1  
select type: SIMPLE  
table: c  
type: ref  
possible_keys: PRIMARY,parent_id  
key: parent_id  
key_len: 4  
ref: test.p.parent_id  
rows: 1  
Extra: Using where  

通過SHOW STATUS 命令可以檢視實際的行操作,該命令表明物理讀運算元量大幅增加。請看下面的示例:

mysql> SHOW SESSION STATUS LIKE 'Handler_read%';  
+--------------------------------------+---------+  
| Variable_name | Value |  
+--------------------------------------+---------+  
| Handler_read_first | 1 |  
| Handler_read_key | 164 |  
| Handler_read_last | 0 |  
| Handler_read_next | 107 |  
| Handler_read_prev | 0 |  
| Handler_read_rnd | 0 |  
| Handler_read_rnd_next | 161 |  
+--------------------------------------+---------+  
相關的QEP 列還包括key列。

2.10、filtered

  在mysql5.1裡新加的,在使用explain extended時出現。它顯示的是針對表裡符合條件的記錄數的百分比所做的一個悲觀估算值。

  filtered 列給出了一個百分比的值,這個百分比值和rows 列的值相乘,可以估計出那些將要和QEP 中的前一個表進行連線的行的數目。前一個表就是指id 列的值比當前表的id 小的表。這一列只有在EXPLAIN EXTENDED 語句中才會出現

2.11、Extra 列

  顯示上述資訊之外的其它資訊,但卻很重要。Extra 列可以包含多個值,可以有很多不同的取值,並且這些值還在隨著MySQL 新版本的釋出而進一步增加。下面給出常