1. 程式人生 > >Explain結果解讀與實踐

Explain結果解讀與實踐

目前 eas variable fill 獲取 解析 dep des admin

 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 RESULT、DEPENDENT 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 新版本的發布而進一步增加。下面給出常用值的列表。你可以從下面的地址找到更全面的值的列表:http://dev.mysql.com/doc/refman/5.5/en/explain-output.html。

2.11.1 Distinct MySQL發現第1個匹配行後,停止為當前的行組合搜索更多的行。一直沒見過這個值

2.11.2 Not exists

2.11.3 range checked for each record

沒有找到合適的索引

2.11.4 using filesort

  若查詢所需的排序與使用的索引的排序一致,因為索引是已排序的,因此按索引的順序讀取結果返回,否則,在取得結果後,還需要按查詢所需的順序對結果進行排序,這時就會出現 Using filesort。需要優化

建表及插入數據:
create table a19(a_id int, b_id int);
insert into a19 values(1,1),(1,1),(2,1),(2,2),(3,1);
mysql> explain select * from a19 order by a_id;
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+----------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+----------------+
| 1 | SIMPLE | a19 | NULL | ALL | NULL | NULL | NULL | NULL | 5 | 100.00 | Using filesort |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+----------------+
對於沒有索引的表,只要 order by 必會出現 Using filesort 。

現在增加索引:create index a_id on a19(a_id);
把表 a 的記錄增加到約 100w(1048576) 條, a_id 與 b_id 都是隨機生成的數字:

mysql> select * from a order by rand() limit 10;
+-------+--------+
| a_id | b_id |
+-------+--------+
| 61566 | 961297 |
| 33951 | 680542 |
| ..... | ...... |
+-------+--------+
mysql> explain select * from a19 order by rand() limit 10;
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+---------------------------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+---------------------------------+
| 1 | SIMPLE | a19 | NULL | ALL | NULL | NULL | NULL | NULL | 5 | 100.00 | Using temporary; Using filesort |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+---------------------------------+
同樣是 Using filesort ,type 為 ALL ,全表掃描。聽說“取全表數據根據ID排序,走索引一定不如直接查,因為可以減少因為需要索引改變數據訪問順序造成隨機IO的概率,數據庫放棄索引是應該的”,參考:
http://isky000.com/database/mysql_order_by_implement#comment-2981

當 type 為 rang、 ref 或者 index 的時候才有可能利用索引排序,其它,如 ALL ,都無法通過索引排序,此時若有 order by ,如上例,便會出現 Using filesort 。

現在增加 where 子句:
mysql> explain select * from a19 where a_id=10 order by a_id;
+----+-------------+-------+------------+------+---------------+------+---------+-------+------+----------+-------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+------+---------------+------+---------+-------+------+----------+-------+
| 1 | SIMPLE | a19 | NULL | ref | a_id | a_id | 5 | const | 1 | 100.00 | NULL |
+----+-------------+-------+------------+------+---------------+------+---------+-------+------+----------+-------+
查詢走了索引 a_id ,此時 type 為 ref ,直接按索引順序返回,沒有 Using filesort 。
修改 where 子句:
mysql> explain select * from a19 where a_id>10 and a_id<100 order by a_id;
+----+-------------+-------+------------+-------+---------------+------+---------+------+------+----------+-----------------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+-------+---------------+------+---------+------+------+----------+-----------------------+
| 1 | SIMPLE | a19 | NULL | range | a_id | a_id | 5 | NULL | 1 | 100.00 | Using index condition |
+----+-------------+-------+------------+-------+---------------+------+---------+------+------+----------+-----------------------+
同樣利用索引排序,沒有 Using filesort 。

再修改 where 子句:
mysql> explain select * from a19 where a_id >1 order by a_id;
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-----------------------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-----------------------------+
| 1 | SIMPLE | a19 | NULL | ALL | a_id | NULL | NULL | NULL | 5 | 60.00 | Using where; Using filesort |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-----------------------------+

又出現 Using filesort 且 type 變為 ALL 。註意以上例子的 rows 列,此列表示 MySQL 估計查詢需要讀取的行數,分別為 1048576, 8, 712, 1048576 ,特別註意最後兩個數字: 712, 1048576 。

可見,當索引能為查詢排除大部份行時( a_id=10 時約讀取 8 行,排除了大部份, a_id>10 and a_id<100 時約讀取 712 行,同樣排除了大部份)便使用索引,否則,如 a_id>10 時約讀取 1048576 , MySQL 直接改用全表掃描,再 Using filesort 。也就是說, MySQL 會根據表中的信息及查詢來決定使用任種方式。

關於 MySQL 讀取數據表的方式,可參考(暫缺參考資料),就會明白為什麽需讀取 1048576 行時,先讀索引再讀表數據還不如全表掃描了。

對於多字段排序(order by a, b)及帶 group by 的查詢,可參考 MySQL 幫助手冊 7.2.12. MySQL如何優化ORDER BY 。

2.11.5 using index

此查詢使用了覆蓋索引(Covering Index),即通過索引就能返回結果,無需訪問表。若沒顯示"Using index"表示讀取了表數據。

建表及插入數據:
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 | filtered | Extra |
+----+-------------+-------+------------+-------+---------------+---------+---------+------+------+----------+-------------+
| 1 | SIMPLE | a13 | NULL | index | NULL | PRIMARY | 4 | NULL | 2 | 100.00 | Using index |
+----+-------------+-------+------------+-------+---------------+---------+---------+------+------+----------+-------------+
因為 id 為主鍵索引,索引中直接包含了 id 的值,所以無需訪問表,直接查找索引就能返回結果。

mysql> explain select age from a13;
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------+
| 1 | SIMPLE | a13 | NULL | ALL | NULL | NULL | NULL | NULL | 2 | 100.00 | 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 | filtered | Extra |
+----+-------------+-------+------------+-------+---------------+------+---------+------+------+----------+-------------+
| 1 | SIMPLE | a14 | NULL | index | NULL | age | 9 | NULL | 2 | 100.00 | Using index |
+----+-------------+-------+------------+-------+---------------+------+---------+------+------+----------+-------------+
現在索引 age 中也包含了 age 列的值,因此不用訪問表便能返回結果了。

建表:create table a15(id int auto_increment primary key, age int, name char(10));
插入 100w 條數據:insert into a15 value(null, rand()*100000000, ‘jack‘);

2.11.6 using temporary 使用到臨時表

  為了解決查詢,MySQL需要創建一個臨時表來容納結果。典型情況如查詢包含可以按不同情況列出列的GROUP BY和ORDER BY子句時。

出現using temporary就說明語句需要優化了,舉個例子來說

EXPLAIN SELECT ads.id FROM ads, city WHERE   city.city_id = 8005   AND ads.status = ‘online‘   AND city.ads_id=ads.id ORDER BY ads.id desc

id  select_type  table   type    possible_keys   key      key_len  ref                     rows  filtered  Extra                          
------  -----------  ------  ------  --------------  -------  -------  --------------------  ------  --------  -------------------------------
     1  SIMPLE       city    ref     ads_id,city_id  city_id  4        const                   2838    100.00  Using temporary; Using filesort
     1  SIMPLE       ads     eq_ref  PRIMARY         PRIMARY  4        city.ads_id       1    100.00  Using where    

這條語句會使用using temporary,而下面這條語句則不會

EXPLAIN SELECT ads.id FROM ads, city WHERE   city.city_id = 8005   AND ads.status = ‘online‘   AND city.ads_id=ads.id ORDER BY city.ads_id desc

id  select_type  table   type    possible_keys   key      key_len  ref                     rows  filtered  Extra                      
------  -----------  ------  ------  --------------  -------  -------  --------------------  ------  --------  ---------------------------
     1  SIMPLE       city    ref     ads_id,city_id  city_id  4        const                   2838    100.00  Using where; Using filesort
     1  SIMPLE       ads    eq_ref  PRIMARY         PRIMARY  4        city.ads_id       1    100.00  Using where    

這是為什麽呢?他倆之間只是一個order by不同,MySQL 表關聯的算法是 Nest Loop Join,是通過驅動表的結果集作為循環基礎數據,然後一條一條地通過該結果集中的數據作為過濾條件到下一個表中查詢數據,然後合並結果。EXPLAIN 結果中,第一行出現的表就是驅動表(Important!)以上兩個查詢語句,驅動表都是 city,如上面的執行計劃所示!

對驅動表可以直接排序,對非驅動表(的字段排序)需要對循環查詢的合並結果(臨時表)進行排序(Important!) 因此,order by ads.id desc 時,就要先 using temporary 了! 驅動表的定義 wwh999 在 2006年總結說,當進行多表連接查詢時, [驅動表] 的定義為:
1)指定了聯接條件時,滿足查詢條件的記錄行數少的表為[驅動表];
2)未指定聯接條件時,行數少的表為[驅動表](Important!)。

示例2:

建表及插入數據:
create table a18(a_id int, b_id int);
insert into a18 values(1,1),(1,1),(2,1),(2,2),(3,1);
mysql> explain select distinct a_id from a18;
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-----------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-----------------+
| 1 | SIMPLE | a18 | NULL | ALL | NULL | NULL | NULL | NULL | 5 | 100.00 | Using temporary |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-----------------+
MySQL 使用臨時表來實現 distinct 操作。


2.11.7、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 | filtered | Extra |
+----+-------------+-------+------------+------+---------------+-------+---------+-------+------+----------+-------+
| 1 | SIMPLE | a16 | NULL | ref | num_a | num_a | 4 | const | 2 | 100.00 | 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 | filtered | Extra |
+----+-------------+-------+------------+------+---------------+-------+---------+-------+------+----------+-------------+
| 1 | SIMPLE | a16 | NULL | ref | num_a | num_a | 4 | const | 2 | 25.00 | 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 | 不符合
| ... | ... | ...
+-------+-------+

在《高性能 MySQL 》(第二版)P144(pdf.167) 頁有更形象的說明圖片(圖 4-5 MySQL 通過整表掃描查找數據)。

字段是否允許 NULL 對 Using where 的影響:

建表及插入數據:
create table a17 (num_a int null, num_b int null, key(num_a));
insert into a17 value(1,1),(1,2),(2,1),(2,2);

這次 num_a, num_b 字段允許為空。

在上例 num_a not null 時, num_a 索引的長度 key_len 為 4 ,當 num_a null 時, num_a 索引的長度變為了 5 :

mysql> explain select * from a17 where num_a is null;
+----+-------------+-------+------------+------+---------------+-------+---------+-------+------+----------+-----------------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+------+---------------+-------+---------+-------+------+----------+-----------------------+
| 1 | SIMPLE | a17 | NULL | ref | num_a | num_a | 5 | const | 1 | 100.00 | Using index condition |
+----+-------------+-------+------------+------+---------------+-------+---------+-------+------+----------+-----------------------+
1 row in set, 1 warning (0.00 sec)

mysql> explain select * from a17 where num_a=3;
+----+-------------+-------+------------+------+---------------+-------+---------+-------+------+----------+-------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+------+---------------+-------+---------+-------+------+----------+-------+
| 1 | SIMPLE | a17 | NULL | ref | num_a | num_a | 5 | const | 1 | 100.00 | NULL |
+----+-------------+-------+------------+------+---------------+-------+---------+-------+------+----------+-------+

2.11.8、Using join buffer

這個值強調了在獲取連接條件時沒有使用索引,並且需要連接緩沖區來存儲中間結果。如果出現了這個值,那應該註意,根據查詢的具體情況可能需要添加索引來改進性能。

2.11.9. Impossible where

這個值強調了where 語句會導致沒有符合條件的行。請看下面的示例:mysql> EXPLAIN SELECT * FROM user WHERE 1=2;

2.11.10. Select tables optimized away

這個值意味著僅通過使用索引,優化器可能僅從聚合函數結果中返回一行。

2.11.11、 Distinct

這個值意味著MySQL 在找到第一個匹配的行之後就會停止搜索其他行。

2.11.12、 Index merges

當MySQL 決定要在一個給定的表上使用超過一個索引的時候,就會出現以下格式中的一個,詳細說明使用的索引以及合並的類型。

? Using sort_union(…)

? Using union(…)

? Using intersect(…)

EXPLAIN結果中哪些信息要引起關註

我們使用EXPLAIN解析SQL執行計劃時,如果有下面幾種情況,就需要特別關註下了:

首先看下 type 這列的結果,如果有類型是 ALL 時,表示預計會進行全表掃描(full table scan)。通常全表掃描的代價是比較大的,建議創建適當的索引,通過索引檢索避免全表掃描。此外,全索引掃描(full index scan)的代價有時候是比全表掃描還要高的,除非是基於InnoDB表的主鍵索引掃描。

再來看下 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 則都還好,不用緊張。

Explain結果解讀與實踐