1. 程式人生 > 資料庫 >MySQL查詢執行計劃詳解——EXPLAIN

MySQL查詢執行計劃詳解——EXPLAIN

MySQL查詢執行計劃詳解——explain

一、前言

本文來自

explain用於解釋優化器在執行select、update、delete、insert、replace語句時的執行計劃,即它解釋了MySQL如何處理SQL語句,包括表如何連線、表的連線順序、用了哪些索引等。(replace是MySQL對於標準SQL語句的擴充套件,其他資料庫可能沒有,replace的使用見)

本文使用的表結構和資料如下:

CREATE DATABASE test;

CREATE TABLE trb1 (
    id int auto_increment primary key,
    name varchar(50),
    purchased date
)
PARTITION BY RANGE (ID) (
    PARTITION p0 VALUES LESS THAN (3),
    PARTITION P1 VALUES LESS THAN (7),
    PARTITION P2 VALUES LESS THAN (9),
    PARTITION P3 VALUES LESS THAN (11)
);

INSERT INTO trb1 VALUES
(1, 'desk organiser', '2003-10-15'),
(2, 'CD player', '1993-11-05'),
(3, 'TV set', '1996-03-10'),
(4, 'bookcase', '1982-01-10'),
(5, 'exercise bike', '2004-05-09'),
(6, 'sofa', '1987-06-05'),
(7, 'popcorn maker', '2001-11-22'),
(8, 'aquarium', '1992-08-04'),
(9, 'study desk', '1984-09-16'),
(10, 'lava lamp', '1998-12-25');

create table trb2(
    id2 int auto_increment primary key,
    id int
);

insert into trb2(id) values(3), (3), (4);

create table trb3(
    id3 int,
    name varchar(50),
    purchased date,
    primary key(id3, name),
    index trb3_index1(name, purchased)
);

insert into trb3 values
(1, 'desk organiser', '2003-10-15'),
(2, 'CD player', '1993-11-05'),
(3, 'TV set', '1996-03-10'),
(4, 'bookcase', '1982-01-10'),
(5, 'exercise bike', '2004-05-09'),
(6, 'sofa', '1987-06-05'),
(7, 'popcorn maker', '2001-11-22'),
(8, 'aquarium', '1992-08-04'),
(9, 'study desk', '1984-09-16'),
(10, 'lava lamp', '1998-12-25');

文章目錄:

目錄

二、explain輸出格式解釋

EXPLAIN為SELECT語句中使用的每個表返回一行資訊,按照MySQL在處理語句時讀取它們的順序列示。explain的輸出列如下。

Column JSON Name Meaning
id select_id 查詢的唯一標識,
select_type None select型別
table table_name 表名,如設定了別名(alias)則展示別名
partitions partitions 查詢計劃匹配到的分割槽
type access_type 連線型別
possible_keys possible_keys 可能使用的索引
key key 實際使用的索引
key_len key_length 實際使用的索引的位元組長度
ref ref 與索引比較的列
rows rows 估計要檢查的行數量
filtered filtered 按表條件過濾的行百分比
Extra None 額外資訊

1、id

MySQL會給每一個查詢分配一個id,歸屬同一個查詢的行則該標識相同,不同的查詢按序號順序列示。注意並不是每有一個select就會有一個獨立的id,如下:

mysql> explain select * from trb1 t1 where exists(select 1 from trb2 t2 where t2.id = t1.id); -- id相同
+----+-------------+-------+-------------+------+---------------+------+---------+------+------+----------+-------------------------------------------------------------------+
| id | select_type | table | partitions  | type | possible_keys | key  | key_len | ref  | rows | filtered | Extra
                            |
+----+-------------+-------+-------------+------+---------------+------+---------+------+------+----------+-------------------------------------------------------------------+
|  1 | SIMPLE      | t2    | NULL        | ALL  | NULL          | NULL | NULL    | NULL |    3 |   100.00 | Start temporary
                            |
|  1 | SIMPLE      | t1    | p0,p1,p2,p3 | ALL  | NULL          | NULL | NULL    | NULL |   10 |    10.00 | Using where; End temporary; Using join buffer (Block Nested Loop) |
+----+-------------+-------+-------------+------+---------------+------+---------+------+------+----------+-------------------------------------------------------------------+
2 rows in set, 2 warnings (0.00 sec)

mysql> explain select t1.*, (select t2.id2 from trb2 t2 where t2.id = t1.id limit 1) as id2 from trb1 t1; -- id不同
+----+--------------------+-------+-------------+------+---------------+------+---------+------+------+----------+-------------+
| id | select_type        | table | partitions  | type | possible_keys | key  | key_len | ref  | rows | filtered | Extra       |
+----+--------------------+-------+-------------+------+---------------+------+---------+------+------+----------+-------------+
|  1 | PRIMARY            | t1    | p0,p1,p2,p3 | ALL  | NULL          | NULL | NULL    | NULL |   10 |   100.00 | NULL        |
|  2 | DEPENDENT SUBQUERY | t2    | NULL        | ALL  | NULL          | NULL | NULL    | NULL |    3 |    33.33 | Using where |
+----+--------------------+-------+-------------+------+---------------+------+---------+------+------+----------+-------------+
2 rows in set, 2 warnings (0.00 sec)

2、select_type

select_type Value JSON Name Meaning
SIMPLE None 簡單的select語句,沒使用union或子查詢
PRIMARY None 最外層的select語句
UNION None union中的第二個或更後面的select語句
DEPENDENT UNION dependent (true) union中的第二個或更後面的select語句,依賴於外部查詢(不理解)
UNION RESULT union_result union的結果,把union查詢當作一個臨時表的結果
SUBQUERY None 子查詢的第一個select語句
DEPENDENT SUBQUERY dependent (true) 子查詢的第一個select語句,依賴於外部查詢
DERIVED None 派生表,臨時表
DEPENDENT DERIVED dependent (true) 依賴於另外一個表的臨時表
MATERIALIZED materialized_from_subquery 物化子查詢
UNCACHEABLE SUBQUERY cacheable (false) 一個無法快取的子查詢,外部查詢的每一行都要重新執行子查詢
UNCACHEABLE UNION cacheable (false) 屬於一個uncacheable subquery的union查詢的第二個或更後面的查詢

看下面這個查詢。

id為1的是外部主查詢,表名是,即id為3的derived型別的表,即別名為b的表,explain中沒把b展示出來。id為2的是一個臨時表,其表名為table1,可能是因為使用了union,展示的union中的第一個表名。

id為4的是一個uncacheable union,即無法被快取的子查詢,且存在於一個union中,處於union的第二個或更後面的位置。因為order by rand(),每次都要重新執行這次查詢才能獲取結果,因此無法被快取。

id為NULL的是一個union結果,表名為<union3,4>,即union了id為3和4兩張表的結果。

id為2的是一個UNCACHEABLE SUBQUERY,解釋如id為4的一樣。

mysql> EXPLAIN select b.*, (SELECT table3.id as c from trb1 table3 order by rand() limit 1) AS c FROM ( (select table1.id as a from trb1 table1 order by rand() LIMIT 1) UNION (select table2.id as a from trb1 table2 order by rand() LIMIT 1) ) as b;
+----+----------------------+------------+-------------+------+---------------+------+---------+------+------+----------+---------------------------------+
| id | select_type          | table      | partitions  | type | possible_keys | key  | key_len | ref  | rows | filtered | Extra
    |
+----+----------------------+------------+-------------+------+---------------+------+---------+------+------+----------+---------------------------------+
|  1 | PRIMARY              | <derived3> | NULL        | ALL  | NULL          | NULL | NULL    | NULL |    2 |   100.00 | NULL
    |
|  3 | DERIVED              | table1     | p0,p1,p2,p3 | ALL  | NULL          | NULL | NULL    | NULL |   10 |   100.00 | Using temporary; Using filesort |
|  4 | UNCACHEABLE UNION    | table2     | p0,p1,p2,p3 | ALL  | NULL          | NULL | NULL    | NULL |   10 |   100.00 | Using temporary; Using filesort |
| NULL | UNION RESULT         | <union3,4> | NULL        | ALL  | NULL          | NULL | NULL    | NULL | NULL |     NULL | Using temporary
      |
|  2 | UNCACHEABLE SUBQUERY | table3     | p0,p1,p2,p3 | ALL  | NULL          | NULL | NULL    | NULL |   10 |   100.00 | Using temporary; Using filesort |
+----+----------------------+------------+-------------+------+---------------+------+---------+------+------+----------+---------------------------------+
5 rows in set, 1 warning (0.00 sec)

當子查詢中依賴外部表來獲取結果時,就會有一個dependent,如下。

mysql> explain select t1.*, (select t2.id2 from trb2 t2 where t2.id = t1.id limit 1) as id2 from trb1 t1;
+----+--------------------+-------+-------------+------+---------------+------+---------+------+------+----------+-------------+
| id | select_type        | table | partitions  | type | possible_keys | key  | key_len | ref  | rows | filtered | Extra       |
+----+--------------------+-------+-------------+------+---------------+------+---------+------+------+----------+-------------+
|  1 | PRIMARY            | t1    | p0,p1,p2,p3 | ALL  | NULL          | NULL | NULL    | NULL |   10 |   100.00 | NULL        |
|  2 | DEPENDENT SUBQUERY | t2    | NULL        | ALL  | NULL          | NULL | NULL    | NULL |    3 |    33.33 | Using where |
+----+--------------------+-------+-------------+------+---------------+------+---------+------+------+----------+-------------+
2 rows in set, 2 warnings (0.00 sec)

DEPENDENT SUBQUERY評估與UNCACHEABLE SUBQUERY評估不同:對於DEPENDENT SUBQUERY,子查詢僅針對其外部上下文中變數的每組不同值重新評估一次。對於UNCACHEABLE SUBQUERY,將為外部上下文的每一行重新評估子查詢。即DEPENDENT SUBQUERY是有一部分快取的。

3、table

table指的是表名或別名,或其他形式名稱。(<DERIVEDN>,<UNIONM,N>,<subqueryN>,加粗字母均為表對應的id。具體見上面的分析)

4、partition

partition指的是該查詢所使用到的表分割槽。關於表分割槽的解釋見。如上面那個trb1表使用了所有的分割槽,又如下面這個查詢只用到了p0、p1分割槽。

mysql> explain select * from trb1 where id < 5;
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key  | key_len | ref  | rows | filtered | Extra       |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+
|  1 | SIMPLE      | trb1  | p0,p1      | ALL  | NULL          | NULL | NULL    | NULL |    6 |    33.33 | Using where |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+
1 row in set, 1 warning (0.00 sec)

5、type

type指的是join type,即表之間是如何連線的。下面從最優到最差的方式排序列出了各種連線方式。

  • system:指表中只有一條記錄,且符合const型別的查詢。是一種特殊const型別。

  • const:指通過primary key或unique查詢出來的資料,最多隻有一條記錄匹配。

  • eq_ref:指該表通過完整的primary key或uniqeu not null去和其他表相應欄位連線時,則該表的join type為qe_ref,另外一張表的join type根據另外一張表的key去判斷。這種情況下可以保證最多隻能匹配出一條記錄。如下,trb1表是eq_ref,而trb2表的join type是ALL,全表掃描。

mysql> explain select * from trb1, trb2 where trb1.id = trb2.id;
+----+-------------+-------+-------------+--------+---------------+---------+---------+--------------+------+----------+-------------+
| id | select_type | table | partitions  | type   | possible_keys | key     | key_len | ref          | rows | filtered | Extra       |
+----+-------------+-------+-------------+--------+---------------+---------+---------+--------------+------+----------+-------------+
|  1 | SIMPLE      | trb2  | NULL        | ALL    | NULL          | NULL    | NULL    | NULL         |    3 |   100.00 | Using where |
|  1 | SIMPLE      | trb1  | p0,P1,P2,P3 | eq_ref | PRIMARY       | PRIMARY | 4       | test.trb2.id |    1 |   100.00 | NULL        |
+----+-------------+-------+-------------+--------+---------------+---------+---------+--------------+------+----------+-------------+
2 rows in set, 1 warning (0.00 sec)
  • ref:指該表通過某個索引的最左字首的部分或完整欄位或多欄位主鍵中滿足最左字首的部分欄位去和其他表字段連線時,則該表的join type為ref。這種情況下無法保證匹配出最多一條記錄。如下:
mysql> explain select * from trb3 where id3 = 1;
+----+-------------+-------+------------+------+---------------+---------+---------+-------+------+----------+-------+
| id | select_type | table | partitions | type | possible_keys | key     | key_len | ref   | rows | filtered | Extra |
+----+-------------+-------+------------+------+---------------+---------+---------+-------+------+----------+-------+
|  1 | SIMPLE      | trb3  | NULL       | ref  | PRIMARY       | PRIMARY | 4       | const |    1 |   100.00 | NULL  |
+----+-------------+-------+------------+------+---------------+---------+---------+-------+------+----------+-------+
1 row in set, 1 warning (0.00 sec)

mysql> explain select t3.*, t2.id2 from trb3 t3, trb2 t2 where t3.id3 = t2.id2;
+----+-------------+-------+------------+-------+---------------+---------+---------+-------------+------+----------+-------------+
| id | select_type | table | partitions | type  | possible_keys | key     | key_len | ref         | rows | filtered | Extra       |
+----+-------------+-------+------------+-------+---------------+---------+---------+-------------+------+----------+-------------+
|  1 | SIMPLE      | t2    | NULL       | index | PRIMARY       | PRIMARY | 4       | NULL        |    3 |   100.00 | Using index |
|  1 | SIMPLE      | t3    | NULL       | ref   | PRIMARY       | PRIMARY | 4       | test.t2.id2 |    1 |   100.00 | NULL        |
+----+-------------+-------+------------+-------+---------------+---------+---------+-------------+------+----------+-------------+
2 rows in set, 1 warning (0.00 sec)
  • fulltext:使用全文索引。大多業務使用較少,除非那種需要檢索大量文字的業務。全文索引僅排在ref後面,說明全文索引的效率很高。
    全文索引大概意思就是專門用於文字查詢的一個索引,只能構建在char、varchar、text型別上,通過全文索引的查詢有自己特殊的語法(match(index_column) again('xxxx')),全文索引的檢索有最小搜尋長度和最大搜索長度限制(當然可以通過修改my.ini修改配置),表的行數量條件要求,以及各種殷勤和版本限制等。關於全文索引的具體介紹見。

  • ref_or_null:即在ref情況下,使用索引的後面一個或多個欄位使用is null來匹配,如下。

SELECT * FROM ref_table
  WHERE key_column=expr OR key_column IS NULL;
  • index_merge:索引合併。索引合併大概意思就是查詢使用了多個索引並且可以合併這些索引以查詢資料。具體見。如下就是一個索引合併。
mysql> explain select * from trb3 where id3 = 1 or name = 'CD player';
+----+-------------+-------+------------+-------------+---------------------+---------------------+---------+------+------+----------+----------------------------------------------------+
| id | select_type | table | partitions | type        | possible_keys       | key                 | key_len | ref  | rows | filtered | Extra
                                    |
+----+-------------+-------+------------+-------------+---------------------+---------------------+---------+------+------+----------+----------------------------------------------------+
|  1 | SIMPLE      | trb3  | NULL       | index_merge | PRIMARY,trb3_index1 | trb3_index1,PRIMARY | 202,4   | NULL |    2 |   100.00 | Using sort_union(trb3_index1,PRIMARY); Using where |
+----+-------------+-------+------------+-------------+---------------------+---------------------+---------+------+------+----------+----------------------------------------------------+
1 row in set, 1 warning (0.00 sec)
  • unique_subquery:官方文件說是這種是在IN子查詢中使用索引覆蓋,以提高效率,如下,但是本人實際測試中並未使用這種join type。
value IN (SELECT primary_key FROM single_table WHERE some_expr)
  • index_subquery:和上一個相似,區別是不用primary_key,而是使用普通的索引。
value IN (SELECT key_column FROM single_table WHERE some_expr)
  • range:索引被用作範圍查詢時可能使用range型別,有以下幾點值得注意:
    • join type為range時,key_len為使用的索引的最大長度
    • join type為range時,ref欄位為NULL
    • =, <>, >, >=, <, <=, IS NULL, <=>, BETWEEN, LIKE, or IN() 都可能會使用range
    • 索引用作範圍查詢時並不一定使用range,也可能使用其他,優化器會根據實際情況選擇。如下第一個SQL即使用了index。
    • range也要滿足最左字首原則,不滿足則可能使用其他型別,如下方程式碼塊中的最後一個SQL。
mysql> explain select * from trb3 where name > 'aa';
+----+-------------+-------+------------+-------+---------------+-------------+---------+------+------+----------+--------------------------+
| id | select_type | table | partitions | type  | possible_keys | key         | key_len | ref  | rows | filtered | Extra                    |
+----+-------------+-------+------------+-------+---------------+-------------+---------+------+------+----------+--------------------------+
|  1 | SIMPLE      | trb3  | NULL       | index | trb3_index1   | trb3_index1 | 206     | NULL |   10 |   100.00 | Using where; Using index |
+----+-------------+-------+------------+-------+---------------+-------------+---------+------+------+----------+--------------------------+
1 row in set, 1 warning (0.00 sec)

mysql> explain select * from trb3 where name > 'sofa';
+----+-------------+-------+------------+-------+---------------+-------------+---------+------+------+----------+--------------------------+
| id | select_type | table | partitions | type  | possible_keys | key         | key_len | ref  | rows | filtered | Extra                    |
+----+-------------+-------+------------+-------+---------------+-------------+---------+------+------+----------+--------------------------+
|  1 | SIMPLE      | trb3  | NULL       | range | trb3_index1   | trb3_index1 | 202     | NULL |    2 |   100.00 | Using where; Using index |
+----+-------------+-------+------------+-------+---------------+-------------+---------+------+------+----------+--------------------------+
1 row in set, 1 warning (0.00 sec)

mysql> explain select * from trb3 where name > 'sofa' and purchased < '2020-01-01';
+----+-------------+-------+------------+-------+---------------+-------------+---------+------+------+----------+--------------------------+
| id | select_type | table | partitions | type  | possible_keys | key         | key_len | ref  | rows | filtered | Extra                    |
+----+-------------+-------+------------+-------+---------------+-------------+---------+------+------+----------+--------------------------+
|  1 | SIMPLE      | trb3  | NULL       | range | trb3_index1   | trb3_index1 | 202     | NULL |    2 |    33.33 | Using where; Using index |
+----+-------------+-------+------------+-------+---------------+-------------+---------+------+------+----------+--------------------------+
1 row in set, 1 warning (0.00 sec)

mysql> explain select * from trb3 where purchased < '2020-01-01';
+----+-------------+-------+------------+-------+---------------+-------------+---------+------+------+----------+--------------------------+
| id | select_type | table | partitions | type  | possible_keys | key         | key_len | ref  | rows | filtered | Extra                    |
+----+-------------+-------+------------+-------+---------------+-------------+---------+------+------+----------+--------------------------+
|  1 | SIMPLE      | trb3  | NULL       | index | trb3_index1   | trb3_index1 | 206     | NULL |   10 |    33.33 | Using where; Using index |
+----+-------------+-------+------------+-------+---------------+-------------+---------+------+------+----------+--------------------------+
1 row in set, 1 warning (0.00 sec)
  • index:雖然使用了索引中的欄位進行查詢,但是不滿足最左字首原則,則MySQL會在索引樹中全樹掃描,這就是index,如上面程式碼塊中的第一個SQL和最後一個SQL。最後一個SQL使用index好理解,第一個SQL使用index個人認為是優化器發現索引樹中的最小值 > 查詢條件'aa',因此name > 'aa'就等於全樹掃描,所以為index。如下當條件為name > 'ar'時,就使用了range,且任何條件大於索引樹中的最小值的查詢,都會使用range,而小於則全樹掃描。
    index的效率比ALL高一點點,畢竟掃描全樹的IO事件比掃描全表的IO事件更少。
mysql> explain select * from trb3 where name > 'ar';
+----+-------------+-------+------------+-------+---------------+-------------+---------+------+------+----------+--------------------------+
| id | select_type | table | partitions | type  | possible_keys | key         | key_len | ref  | rows | filtered | Extra                    |
+----+-------------+-------+------------+-------+---------------+-------------+---------+------+------+----------+--------------------------+
|  1 | SIMPLE      | trb3  | NULL       | range | trb3_index1   | trb3_index1 | 202     | NULL |    9 |   100.00 | Using where; Using index |
+----+-------------+-------+------------+-------+---------------+-------------+---------+------+------+----------+--------------------------+
1 row in set, 1 warning (0.00 sec)
  • ALL:全表掃描,未走任何索引,效率最低。

6、possible key

可能使用的索引,沒啥好講的。

7、key

實際使用的索引,也沒啥好講。

index hint是指,讓MySQL按照我們的給定的索引去查詢資料,主要有force index、use index和ignore index,也可以加上for join | order by | group by來指定索引使用的範圍,如下SQL。具體使用見。

force index和use index的區別是:force index會強制使用該索引,但use index是建議MySQL使用該索引,但是優化器還是會根據實際情況來選擇是否要全表掃描。

mysql> explain select * from trb3 force index for order by (trb3_index1) where id3 > 3 order by name;
+----+-------------+-------+------------+-------+---------------------+---------+---------+------+------+----------+-----------------------------+
| id | select_type | table | partitions | type  | possible_keys       | key     | key_len | ref  | rows | filtered | Extra                       |
+----+-------------+-------+------------+-------+---------------------+---------+---------+------+------+----------+-----------------------------+
|  1 | SIMPLE      | trb3  | NULL       | range | PRIMARY,trb3_index1 | PRIMARY | 4       | NULL |    7 |   100.00 | Using where; Using filesort |
+----+-------------+-------+------------+-------+---------------------+---------+---------+------+------+----------+-----------------------------+
1 row in set, 1 warning (0.00 sec)

8、key_len

該欄位指的是查詢執行時實際使用的索引的總最大位元組長度,當所有有多個欄位時,可以通過這個來看一個查詢具體使用了哪幾個欄位。int佔用4個位元組,varchar每一個字元佔用4個位元組和2個字元儲存字串長度(varchar中文實際上大部分佔3個位元組,少量才佔用四個位元組,這裡按最大的算),因此varchar(50)佔用202個位元組。如下通過檢視位元組長度就可以發現使用了多欄位索引中的哪幾個欄位。

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

mysql> explain select * from trb3 where name > 'sdf';
+----+-------------+-------+------------+-------+---------------+-------------+---------+------+------+----------+--------------------------+
| id | select_type | table | partitions | type  | possible_keys | key         | key_len | ref  | rows | filtered | Extra                    |
+----+-------------+-------+------------+-------+---------------+-------------+---------+------+------+----------+--------------------------+
|  1 | SIMPLE      | trb3  | NULL       | range | trb3_index1   | trb3_index1 | 202     | NULL |    5 |   100.00 | Using where; Using index |
+----+-------------+-------+------------+-------+---------------+-------------+---------+------+------+----------+--------------------------+
1 row in set, 1 warning (0.00 sec)

mysql> explain select * from trb3 where id3 = 3 and name > 'a';
+----+-------------+-------+------------+-------+---------------------+---------+---------+------+------+----------+-------------+
| id | select_type | table | partitions | type  | possible_keys       | key     | key_len | ref  | rows | filtered | Extra       |
+----+-------------+-------+------------+-------+---------------------+---------+---------+------+------+----------+-------------+
|  1 | SIMPLE      | trb3  | NULL       | range | PRIMARY,trb3_index1 | PRIMARY | 206     | NULL |    1 |   100.00 | Using where |
+----+-------------+-------+------------+-------+---------------------+---------+---------+------+------+----------+-------------+
1 row in set, 1 warning (0.00 sec)

9、ref

該欄位顯示了用哪些列或常量來和索引欄位去匹配以查詢資料。當索引和一個常量匹配時,ref欄位為const,當使用索引行進範圍查詢時,ref欄位為NULL。

mysql> explain select * from trb2 where id2 in (select id from trb1 where name = 'aquarium');
+----+-------------+-------+-------------+--------+---------------+---------+---------+---------------+------+----------+-------------+
| id | select_type | table | partitions  | type   | possible_keys | key     | key_len | ref           | rows | filtered | Extra       |
+----+-------------+-------+-------------+--------+---------------+---------+---------+---------------+------+----------+-------------+
|  1 | SIMPLE      | trb2  | NULL        | ALL    | PRIMARY       | NULL    | NULL    | NULL          |    3 |   100.00 | NULL        |
|  1 | SIMPLE      | trb1  | p0,P1,P2,P3 | eq_ref | PRIMARY       | PRIMARY | 4       | test.trb2.id2 |    1 |    10.00 | Using where |
+----+-------------+-------+-------------+--------+---------------+---------+---------+---------------+------+----------+-------------+
2 rows in set, 1 warning (0.00 sec)

mysql> explain select * from trb3 where id3 = if(id3 > 3, 5, 2);
+----+-------------+-------+------------+-------+---------------+-------------+---------+------+------+----------+--------------------------+
| id | select_type | table | partitions | type  | possible_keys | key         | key_len | ref  | rows | filtered | Extra                    |
+----+-------------+-------+------------+-------+---------------+-------------+---------+------+------+----------+--------------------------+
|  1 | SIMPLE      | trb3  | NULL       | index | NULL          | trb3_index1 | 206     | NULL |   12 |    10.00 | Using where; Using index |
+----+-------------+-------+------------+-------+---------------+-------------+---------+------+------+----------+--------------------------+
1 row in set, 1 warning (0.00 sec)

10、rows

rows列表示MySQL認為執行查詢必須檢查的行數。對於InnoDB表,此數字是估計值,可能並不總是準確的。

11、filtered

該列指的是按表條件過濾的錶行的估計百分比。最大值為100,這表示未過濾行。值從100減小表示過濾量增加。rows × filtered的值表示與下表連線的行數。例如,如果行數為1000,過濾條件為50.00(50%),則與下表連線的行數為1000×50%= 500。

12、Extra

該列展示了SQL執行計劃的額外資訊,包括太多的內容,大部分是很少見的,以下主要解釋幾個重要的值得優化的內容:

  • Using filesort:意為MySQL必須額外對檢索出來的資料進行一次排序再輸出這些資料。
    排序是通過根據連線型別遍歷所有行並存儲與WHERE子句匹配的所有行的排序鍵和指向該行的指標來完成的。即排序會using filesort會遍歷所有行,儲存通過where條件篩選出的行的排序欄位和指向該行的指標,再對排序欄位值和指標進行排序,再按照指標順序輸出資料。
    因此這種排序方式是特別慢的,排序優化見。

  • Using index:僅使用索引樹中的資訊從表中檢索列資訊,而不必進行其他查詢以讀取實際行。

  • Using index condition:通過訪問索引集並首先對其進行測試以確定是否需要讀取完整的表。除非有必要整表掃描,否則索引資訊將用於延遲(“下推push down”)再讀取整個錶行。
    索引條件下推(Index Condition Pushdown)是針對MySQL使用using index從表中檢索行的情況的一種優化。如果不使用ICP,則儲存引擎將遍歷索引以在基表中定位行,並將其返回給MySQL伺服器,後者將評估這些行的WHERE條件。啟用ICP後,如果僅可以使用索引中的列來評估WHERE條件的一部分,則MySQL伺服器會將WHERE條件的這一部分下推到儲存引擎。然後,儲存引擎通過使用索引條目來評估推送的索引條件,並且只有在滿足此條件的情況下,才從表中讀取行。 ICP可以減少儲存引擎必須訪問基表的次數以及MySQL伺服器必須訪問儲存引擎的次數。見。

  • Using index for group-by:即有一個索引可以可用於檢索GROUP BY或DISTINCT查詢的所有列,類似於group by的索引覆蓋。

  • Using temporary:使用臨時表。

  • Using where:即存在where條件,且where欄位不在任意一個索引中,不能使用索引樹進行where匹配,而必須在檢查所有行再把滿足where條件的資料輸出給客戶端。

三、explain的擴充套件輸出格式

explain輸出列中的Extra列實際上並不是explain的,而是show warnings的結果,可以在使用explain後,可以緊跟著使用show warnings命令檢視完整的extended information。

8.0.12版本之前,show warnings只適用於select,8.0.12版本之後,它適用於select、delete、update、replace、insert。

show warnings的message列顯示了優化器如何限定select語句中的表名和列名,select語句在應用優化器的優化和重寫之後的樣子(會額外提供一些特殊標記,不一定是有效的SQL),以及其他與優化器處理有關的資訊。如下。

mysql> explain select t1.id, t1.id in (select id from trb2) from trb1 t1;
+----+-------------+-------+-------------+-------+---------------+---------+---------+------+------+----------+-------------+
| id | select_type | table | partitions  | type  | possible_keys | key     | key_len | ref  | rows | filtered | Extra       |
+----+-------------+-------+-------------+-------+---------------+---------+---------+------+------+----------+-------------+
|  1 | PRIMARY     | t1    | p0,P1,P2,P3 | index | NULL          | PRIMARY | 4       | NULL |   10 |   100.00 | Using index |
|  2 | SUBQUERY    | trb2  | NULL        | ALL   | NULL          | NULL    | NULL    | NULL |    3 |   100.00 | NULL        |
+----+-------------+-------+-------------+-------+---------------+---------+---------+------+------+----------+-------------+
2 rows in set, 1 warning (0.00 sec)

mysql> show warnings;
+-------+------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| Level | Code | Message

                                                                                                                   |
+-------+------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| Note  | 1003 | /* select#1 */ select `test`.`t1`.`ID` AS `id`,<in_optimizer>(`test`.`t1`.`ID`,`test`.`t1`.`ID` in ( <materialize> (/* select#2 */ select `test`.`trb2`.`id` from `test`.`trb2` where true having true ), <primary_index_lookup>(`test`.`t1`.`ID` in <temporary table> on <auto_key> where ((`test`.`t1`.`ID` = `materialized-subquery`.`id`))))) AS `t1.id in (select id from trb2)` from `test`.`trb1` `t1` |
+-------+------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
1 row in set (0.00 sec)

show warnings中message列可能包含的特殊標記見,如下。

  • <auto_key>
    為臨時表自動生成的索引。

  • <cache>(expr)
    expr表示式執行一次,將結果儲存在記憶體中備用。對於有很多個快取值,MySQL會建立一個臨時表,並顯示<temporary table>。

  • <exists>(query fragment)
    子查詢將轉換為EXISTS語句。和子查詢優化有關,子查詢優化有物化成臨時表、semi join和轉換成EXISTS語句等優化方法,其中如果使用轉換成EXISTS這種優化方式時,可能就會有<exists>標記(盲猜的)。見官方文件。

  • <in_optimizer> (query fragment)
    指這是一個內部優化器物件,對使用者沒有任何意義。

  • <index_lookup> (query fragment)
    使用索引查詢來處理查詢片段以查詢合格的行。

  • <if>(condition, expr1, expr2)
    if條件,condition條件為真則執行expr1,否則執行expr2。

  • <is_not_null_test>(expr)
    用於驗證表示式是否為null的測試。

  • <materialize>(query fragment)
    物化子查詢,見<exists>。

  • `materialized-subquery`.col_name
    一個子查詢結果被物化成內部臨時表後,這個臨時表對某一列的引用。

  • <primary_index_lookup>(query fragment)
    使用主鍵查詢來處理查詢片段以查詢合格的行。

  • <ref_null_helper>(expr)
    這是一個內部優化器物件,對使用者沒有任何意義。

  • /* select#N */ select_stmt
    指與explain中對應的某一個SELECT語句,N即為explain中的id。

  • outer_tables semi join (inner_tables)
    半聯接操作。inner_tables顯示未拉出的表。見<exists>。

  • <temporary table>(expr)
    為快取結果建立的一個臨時表。

四、explain的其他用法

explain for connection

可以通過show processlist檢視連線執行緒列表,或通過select connection_id()檢視當前連線執行緒的id。

explain for connection connection_id的用處是檢視id為connection_id的執行緒當前正在執行的SQL語句的執行計劃。如果那個執行緒當前沒有執行SQL語句,則結果為空;如果那個執行緒當前執行的SQL語句不是select、update、replace、insert、delete中的任意一個,則會報錯。如下:

mysql> show processlist;
+-----+-----------------+-----------------+-------+---------+--------+------------------------+------------------+
| Id  | User            | Host            | db    | Command | Time   | State                  | Info             |
+-----+-----------------+-----------------+-------+---------+--------+------------------------+------------------+
|   4 | event_scheduler | localhost       | NULL  | Daemon  | 733424 | Waiting on empty queue | NULL             |
| 508 | root            | localhost:2004  | test  | Query   |      0 | starting               | show processlist |
| 509 | root            | localhost:1748  | xxxx | Sleep   |    275 |                        | NULL             |
| 510 | root            | localhost:5639  | xxxx | Sleep   |    275 |                        | NULL             |
| 515 | root            | localhost:13576 | xxxx   | Sleep   |  45170 |                        | NULL             |
| 516 | root            | localhost:13578 | xxxx   | Sleep   |  45170 |                        | NULL             |
+-----+-----------------+-----------------+-------+---------+--------+------------------------+------------------+
6 rows in set (0.00 sec)

mysql> explain for connection 516;
Query OK, 0 rows affected (0.00 sec)

mysql> explain for connection 508;
ERROR 3012 (HY000): EXPLAIN FOR CONNECTION command is supported only for SELECT/UPDATE/INSERT/DELETE/REPLACE

explain table_name

explain table_name = show columns from table_name = describe table_name

mysql> explain trb1;
+-----------+-------------+------+-----+---------+----------------+
| Field     | Type        | Null | Key | Default | Extra          |
+-----------+-------------+------+-----+---------+----------------+
| ID        | int(11)     | NO   | PRI | NULL    | auto_increment |
| name      | varchar(50) | YES  |     | NULL    |                |
| purchased | date        | YES  |     | NULL    |                |
+-----------+-------------+------+-----+---------+----------------+
3 rows in set (0.01 sec)

五、總結

explain的用處其實就是讓MySQL告訴你某一個SQL查詢執行時,優化器會怎麼優化它,儲存引擎會採用怎樣的表連線方式,採用哪些索引,執行該SQL必須掃描的行數量(估計數),和其他可以用於SQL優化的資訊。通過獲取這些資訊,我們就可以發現一個SQL語句執行慢的原因,並作出合理的優化。