执行计划简介
执行计划是指一条SQL
语句在经过MySQL
查询优化器的优化会后,具体的执行方式。MySQL
为我们提供了EXPLAIN
语句,来获取执行计划的相关信息。需要注意的是,EXPLAIN
语句并不会真的去执行相关的语句,而是通过查询优化器对语句进行分析,找出最优的查询方案,并显示对应的信息。
执行计划支持SELECT
, DELETE
, INSERT
, REPLACE
以及 UPDATE
语句。对于SELECT
语句,EXPLAIN
会生成扩展信息,这些信息可以通过紧随EXPLAIN
语句之后的SHOW WARNINGS
语句显示。
执行计划的输出格式
列 | json名称 | 含义 |
---|---|---|
id | select_id | 每个SELECT 语句都会对应一个唯一的标识符id |
select_type | None | SELECT 关键字对应的查询类型 |
table | table_name | 每行输出的表名 |
partitions | partitions | 匹配的分区 |
type | access_type | 表的访问方法 |
possible_keys | possible_keys | 可能用到的索引 |
key | key | 实际用到的索引 |
key_len | key_length | 实际用到的索引长度 |
ref | ref | 当使用索引等值查询时,与索引作比较的列或常量 |
rows | rows | 预计要读取的行数 |
filtered | filtered | 按表条件过滤后,留存的记录数的百分比(过滤后的行数/过滤前行数 ) |
extra | None | 附加信息 |
创建测试用表及并生成数据
- 创建测试用表
表结构如下所示,实际使用中会创建u1,u2两张表,两张表结构一致,除id列外,其它字段随机插入。
CREATE TABLE u1 (
id INT NOT NULL AUTO_INCREMENT,
name VARCHAR(100),
card_no INT,
school VARCHAR(100),
city VARCHAR(100),
district VARCHAR(100),
street VARCHAR(100),
hoppy VARCHAR(100),
PRIMARY KEY (id),
KEY idx_name (name),
UNIQUE KEY idx_card_no (card_no),
KEY idx_school (school),
KEY idx_address(city, district, street)
) Engine=InnoDB CHARSET=utf8;
- 生成数据
先定义一个生成随机数的函数rand_string
:
DELIMITER $$
DROP FUNCTION IF EXISTS rand_string$$
CREATE FUNCTION `rand_string`(num INT) RETURNS varchar(255) CHARSET UTF8
BEGIN
DECLARE origin_str char(52) DEFAULT 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
DECLARE return_str varchar(255) DEFAULT '';
DECLARE i INT DEFAULT 0;
WHILE i < num DO
SET return_str = CONCAT(return_str, SUBSTRING(origin_str , FLOOR(1 + RAND()*52 ),1));
SET i = i +1;
END WHILE;
RETURN return_str;
END $$
DELIMITER ;
创建存储过程
DELIMITER $$
DROP PROCEDURE IF EXISTS gen_user_data$$
CREATE PROCEDURE `gen_user_data`(num INT)
BEGIN
DECLARE i INT DEFAULT 1;
WHILE i <= num DO
INSERT INTO u1(id, name, card_no, school, city, district, street, hoppy) VALUES(i, rand_string(1+FLOOR(RAND()*100)), 10000000+i, rand_string(1+FLOOR(RAND()*100)), rand_string(1+FLOOR(RAND()*100)), rand_string(1+FLOOR(RAND()*100)), rand_string(1+FLOOR(RAND()*100)), rand_string(1+FLOOR(RAND()*100)));
INSERT INTO u2(id, name, card_no, school, city, district, street, hoppy) VALUES(i, rand_string(1+FLOOR(RAND()*100)), 10000000+i, rand_string(1+FLOOR(RAND()*100)), rand_string(1+FLOOR(RAND()*100)), rand_string(1+FLOOR(RAND()*100)), rand_string(1+FLOOR(RAND()*100)), rand_string(1+FLOOR(RAND()*100)));
SET i = i +1;
END WHILE;
END $$
DELIMITER ;
调用存储过程,生成数据
CALL gen_user_data(10000);
执行计划输出各列详解
id
查询语句中每个SELECT
关键字,都会有一个对应的id
。
最简单的查询如下所示,由于只有一个SELECT
关键字,所以只有id
为1
的记录。
mysql> EXPLAIN SELECT * FROM u1 WHERE name = "abc";
+----+-------------+-------+------------+------+---------------+----------+---------+-------+------+----------+-------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+------+---------------+----------+---------+-------+------+----------+-------+
| 1 | SIMPLE | u1 | NULL | ref | idx_name | idx_name | 303 | const | 1 | 100.00 | NULL |
+----+-------------+-------+------------+------+---------------+----------+---------+-------+------+----------+-------+
1 row in set, 1 warning (0.00 sec)
在多表连接的情况下,有可能出现id
相同的情况。比如下面这种情况,虽然只有一个SELECT
关键字,但是连接查询了两张表,由于每张表都有一行输出记录,所以一共有两行记录;但是由于是同一个SELECT
查询出来的,所以id
值相同,都是1
。这种情况下,出现在上面的表叫驱动表,出现在下面的表叫被驱动表。下面的示例中u2
是驱动表,u1
是被驱动表。
mysql> EXPLAIN SELECT * FROM u1 inner join u2 WHERE u1.name = u2.name;
+----+-------------+-------+------------+------+---------------+----------+---------+------------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+------+---------------+----------+---------+------------+------+----------+-------------+
| 1 | SIMPLE | u2 | NULL | ALL | idx_name | NULL | NULL | NULL | 9899 | 100.00 | Using where |
| 1 | SIMPLE | u1 | NULL | ref | idx_name | idx_name | 303 | zz.u2.name | 1 | 100.00 | NULL |
+----+-------------+-------+------------+------+---------------+----------+---------+------------+------+----------+-------------+
2 rows in set, 1 warning (0.00 sec)
如果一条查询语句中,出现多个SELECT
关键字,则id
会依次递增。
比如下面这条查询语句,由于存在子查询,整个查询一共包含两个SELECT
关键字,则id
从1
增加到2
。
mysql> EXPLAIN SELECT * FROM u1 WHERE name in (select name from u2);
+----+--------------+-------------+------------+--------+---------------+------------+---------+------------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+--------------+-------------+------------+--------+---------------+------------+---------+------------+------+----------+-------------+
| 1 | SIMPLE | u1 | NULL | ALL | idx_name | NULL | NULL | NULL | 9818 | 100.00 | Using where |
| 1 | SIMPLE | | NULL | eq_ref | | | 303 | zz.u1.name | 1 | 100.00 | NULL |
| 2 | MATERIALIZED | u2 | NULL | index | idx_name | idx_name | 303 | NULL | 9899 | 100.00 | Using index |
+----+--------------+-------------+------------+--------+---------------+------------+---------+------------+------+----------+-------------+
3 rows in set, 1 warning (0.01 sec)
另外,需要注意的是,子查询有可能被优化为连接查询,如下面这个查询,虽然我们输入的是子查询,但从EXPLAIN
的输出结果看,两行输出的id
相同,均为1
,可以看出,子查询被转化成了连接查询。
mysql> EXPLAIN SELECT * FROM u1 WHERE name in (SELECT name FROM u2 WHERE u1.school = u2.school);
+----+-------------+-------+------------+------+---------------------+------------+---------+--------------+------+----------+-----------------------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+------+---------------------+------------+---------+--------------+------+----------+-----------------------------+
| 1 | SIMPLE | u1 | NULL | ALL | idx_name,idx_school | NULL | NULL | NULL | 9818 | 100.00 | Using where |
| 1 | SIMPLE | u2 | NULL | ref | idx_name,idx_school | idx_school | 303 | zz.u1.school | 1 | 4.55 | Using where; FirstMatch(u1) |
+----+-------------+-------+------------+------+---------------------+------------+---------+--------------+------+----------+-----------------------------+
2 rows in set, 2 warnings (0.00 sec)
对于包含UNION
子句的查询来说,除了会有多个SELECT
关键字外,还会出现一种特殊情况,就像下面这条语句所展示的。
mysql> EXPLAIN SELECT * FROM u1 UNION SELECT * FROM u2;
+----+--------------+------------+------------+------+---------------+------+---------+------+------+----------+-----------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+--------------+------------+------------+------+---------------+------+---------+------+------+----------+-----------------+
| 1 | PRIMARY | u1 | NULL | ALL | NULL | NULL | NULL | NULL | 9818 | 100.00 | NULL |
| 2 | UNION | u2 | NULL | ALL | NULL | NULL | NULL | NULL | 9899 | 100.00 | NULL |
| NULL | UNION RESULT | | NULL | ALL | NULL | NULL | NULL | NULL | NULL | NULL | Using temporary |
+----+--------------+------------+------------+------+---------------+------+---------+------+------+----------+-----------------+
3 rows in set, 1 warning (0.01 sec)
上面语句的输出中包含了id
为NULL
的行,且table
列显示的是
,extra
列显示Using temporary
。
表明,MySQL
将id
为1
和id
为2
的两个查询结果合并起来;Using temporary
表明,MySQL
为这个合并结果创建了一个临时表,为什么要创建临时表呢,主要是为了去重用的。我们知道,UNION
查询的结果是要去重的。如果不需要去重的话,还要不要创建临时表呢,我们用UNION ALL
语句来试一下:
mysql> EXPLAIN SELECT * FROM u1 UNION ALL SELECT * FROM u2;
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------+
| 1 | PRIMARY | u1 | NULL | ALL | NULL | NULL | NULL | NULL | 9818 | 100.00 | NULL |
| 2 | UNION | u2 | NULL | ALL | NULL | NULL | NULL | NULL | 9899 | 100.00 | NULL |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------+
2 rows in set, 1 warning (0.00 sec)
可见,如果不需要去重的话,就不会建立临时表了。
table
执行计划的输出结果中,每行都有对应的表名,表名除了正常的表之外,也可能是以下列出的值:
-
: 本行引用了id
为M
和N
的行的UNION
结果; -
: 本行引用了id
为N
的表所产生的的派生表结果。派生表有可能产生自FROM
语句中的子查询。 -
: 本行引用了id
为N
的表所产生的的物化子查询结果。
关于
和
的例子,我们在讲解id
属性的时候已经展示了,下面展示下
的情况:
mysql> explain SELECT * FROM (SELECT * FROM u1 UNION ALL SELECT * FROM u2) as t;
+----+-------------+------------+------------+------+---------------+------+---------+------+-------+----------+-------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+------------+------------+------+---------------+------+---------+------+-------+----------+-------+
| 1 | PRIMARY | | NULL | ALL | NULL | NULL | NULL | NULL | 19717 | 100.00 | NULL |
| 2 | DERIVED | u1 | NULL | ALL | NULL | NULL | NULL | NULL | 9818 | 100.00 | NULL |
| 3 | UNION | u2 | NULL | ALL | NULL | NULL | NULL | NULL | 9899 | 100.00 | NULL |
+----+-------------+------------+------------+------+---------------+------+---------+------+-------+----------+-------+
3 rows in set, 1 warning (0.00 sec)
这个示例中,table
列为
,说明使用到了id
为2
的派生表。
select_type
根据每个SELECT
所起到的作用,对其进行分类,并为其设置了select_type
属性,这个属性分为以下几种:
类型 | 英文原文 | 描述 |
---|---|---|
SIMPLE | Simple SELECT (not using UNION or subqueries) |
没有用到UNION 或子查询的简单查询 |
PRIMARY | Outermost SELECT |
最外层的SELECT |
UNION | Second or later SELECT statement in a UNION |
UNION 中的第二个或更后面的SELECT |
DEPENDENT UNION | Second or later SELECT statement in a UNION , dependent on outer query |
依赖于外部查询的,UNION 中的第二个或更后面SELECT |
UNION RESULT | Result of a UNION |
UNION 结果 |
SUBQUERY | First SELECT in subquery |
子查询中的第一个SELECT |
DEPENDENT SUBQUERY | First SELECT in subquery, dependent on outer query |
子查询中的第一个SELECT ,依赖于外部查询 |
DERIVED | Derived table | 派生表 |
MATERIALIZED | Materialized subquery | 物化子查询 |
UNCACHEABLE SUBQUERY | A subquery for which the result cannot be cached and must be re-evaluated for each row of the outer query | 无法缓存结果的子查询,针对外部查询的每一行必须重新进行求值 |
UNCACHEABLE UNION | The second or later select in a UNION that belongs to an uncacheable subquery |
UNION 中属于不可缓存子查询的第二个或更靠后的SELECT |
翻译的不好,大家凑合看哈,下面我们来详细讲解一下。
-
SIMPLE
如果查询中不包括UNION
和子查询,那么这个SELECT
的类型就是SIMPLE
。
比如下面所示的单表查询:
mysql> EXPLAIN SELECT * FROM u1;
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------+
| 1 | SIMPLE | u1 | NULL | ALL | NULL | NULL | NULL | NULL | 9818 | 100.00 | NULL |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------+
1 row in set, 1 warning (0.03 sec)
当然,连接查询也属于SIMPLE
类型:
mysql> EXPLAIN SELECT * FROM u1 INNER JOIN u2;
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+---------------------------------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+---------------------------------------+
| 1 | SIMPLE | u1 | NULL | ALL | NULL | NULL | NULL | NULL | 9818 | 100.00 | NULL |
| 1 | SIMPLE | u2 | NULL | ALL | NULL | NULL | NULL | NULL | 9899 | 100.00 | Using join buffer (Block Nested Loop) |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+---------------------------------------+
2 rows in set, 1 warning (0.00 sec)
-
PRIMARY
一条查询语句中,可能会包含多个SELECT
关键字,最外层的SELECT
,其类型就是PRIMARY
。
mysql> EXPLAIN SELECT * FROM u1 UNION SELECT * FROM u2;
+----+--------------+------------+------------+------+---------------+------+---------+------+------+----------+-----------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+--------------+------------+------------+------+---------------+------+---------+------+------+----------+-----------------+
| 1 | PRIMARY | u1 | NULL | ALL | NULL | NULL | NULL | NULL | 9818 | 100.00 | NULL |
| 2 | UNION | u2 | NULL | ALL | NULL | NULL | NULL | NULL | 9899 | 100.00 | NULL |
| NULL | UNION RESULT | | NULL | ALL | NULL | NULL | NULL | NULL | NULL | NULL | Using temporary |
+----+--------------+------------+------------+------+---------------+------+---------+------+------+----------+-----------------+
3 rows in set, 1 warning (0.00 sec)
从输出可以看到,整个查询由两个SELECT
组成,最外层(最左侧)的SELECT
语句为SELECT * FROM u1
,它的select_type
为PRIMARY
。
UNION
UNION
语句中的第二个或更后的SELECT
关键字,其类型为UNION
。
比如上面那个UNION
查询,最外层的SELECT
其类型为PRIMARY
,第二个SELECT
查询SELECT * FROM u2
其类型就是UNION
。DEPENDENT UNION
与UNION
类似,都是指UNION
语句中的第二个或更后的SELECT
关键字,不过此UNION
查询依赖于外部查询。
mysql> EXPLAIN SELECT * FROM u1 WHERE name in (SELECT name FROM u1 UNION SELECT name FROM u2);
+----+--------------------+------------+------------+------+---------------+----------+---------+------+------+----------+-----------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+--------------------+------------+------------+------+---------------+----------+---------+------+------+----------+-----------------+
| 1 | PRIMARY | u1 | NULL | ALL | NULL | NULL | NULL | NULL | 9818 | 100.00 | Using where |
| 2 | DEPENDENT SUBQUERY | u1 | NULL | ref | idx_name | idx_name | 303 | func | 1 | 100.00 | Using index |
| 3 | DEPENDENT UNION | u2 | NULL | ref | idx_name | idx_name | 303 | func | 1 | 100.00 | Using index |
| NULL | UNION RESULT | | NULL | ALL | NULL | NULL | NULL | NULL | NULL | NULL | Using temporary |
+----+--------------------+------------+------------+------+---------------+----------+---------+------+------+----------+-----------------+
4 rows in set, 1 warning (0.00 sec)
UNION RESULT
MySQL
选择使用临时表来完成UNION
查询的去重工作时,其select_type
就是UNION RESULT
。上面的示例中就有,就不多说了。SUBQUERY
如果子查询没有被转换成连接查询,且是不相关子查询,且查询优化器对该子查询做了物化处理,那么该子查询的第一个SELECT
语句,其select_type
就是SUBQUERY
。
mysql> EXPLAIN SELECT * FROM u1 WHERE name in (SELECT name FROM u2) or name = "abc";
+----+-------------+-------+------------+-------+---------------+----------+---------+------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+-------+---------------+----------+---------+------+------+----------+-------------+
| 1 | PRIMARY | u1 | NULL | ALL | idx_name | NULL | NULL | NULL | 9818 | 100.00 | Using where |
| 2 | SUBQUERY | u2 | NULL | index | idx_name | idx_name | 303 | NULL | 9899 | 100.00 | Using index |
+----+-------------+-------+------------+-------+---------------+----------+---------+------+------+----------+-------------+
2 rows in set, 1 warning (0.00 sec)
:子查询物化是指创建一张临时表来保存子查询结果,以加快查询速度和效率。
MySQL
在首次需要子查询结果时创建临时表,以后再次需要时,则直接从临时表读取,不需要再去重复执行子查询,提高了查询效率。物化时,MySQL
尽可能先在内存中创建临时表,如果表很大,则会在磁盘上创建临时表。
-
DEPENDENT SUBQUERY
如果子查询没有被转换成连接查询,且是相关子查询,那么该子查询的第一个SELECT
关键字代表的查询语句,其select_type
就是DEPENDENT SUBQUERY
。
需要注意的是,对于DEPENDENT SUBQUERY
,子查询针对其外部上下文中变量的每个不同值集重新求值一次,也就是说DEPENDENT SUBQUERY
会被执行多次。
mysql> EXPLAIN SELECT * FROM u1 WHERE name IN (SELECT name FROM u2 where u2.name = u1.name) OR name="abc";
+----+--------------------+-------+------------+------+---------------+----------+---------+------------+------+----------+--------------------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+--------------------+-------+------------+------+---------------+----------+---------+------------+------+----------+--------------------------+
| 1 | PRIMARY | u1 | NULL | ALL | idx_name | NULL | NULL | NULL | 9818 | 100.00 | Using where |
| 2 | DEPENDENT SUBQUERY | u2 | NULL | ref | idx_name | idx_name | 303 | zz.u1.name | 1 | 100.00 | Using where; Using index |
+----+--------------------+-------+------------+------+---------------+----------+---------+------------+------+----------+--------------------------+
2 rows in set, 2 warnings (0.01 sec)
-
DERIVED
派生表一般存在于FROM
子句中。
对于采用物化的方式执行的包含派生表的查询,该派生表对应的子查询的select_type
就是DERIVED
,比方说下边这个查询:
mysql> EXPLAIN SELECT * FROM (SELECT name, school FROM u1 GROUP BY name, school) AS t;
+----+-------------+------------+------------+------+---------------+------+---------+------+------+----------+---------------------------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+------------+------------+------+---------------+------+---------+------+------+----------+---------------------------------+
| 1 | PRIMARY | | NULL | ALL | NULL | NULL | NULL | NULL | 9818 | 100.00 | NULL |
| 2 | DERIVED | u1 | NULL | ALL | NULL | NULL | NULL | NULL | 9818 | 100.00 | Using temporary; Using filesort |
+----+-------------+------------+------------+------+---------------+------+---------+------+------+----------+---------------------------------+
2 rows in set, 1 warning (0.00 sec)
又或者下面这条语句:
mysql> EXPLAIN SELECT * FROM (SELECT * FROM u1 UNION ALL SELECT * FROM u2) as t;
+----+-------------+------------+------------+------+---------------+------+---------+------+-------+----------+-------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+------------+------------+------+---------------+------+---------+------+-------+----------+-------+
| 1 | PRIMARY | | NULL | ALL | NULL | NULL | NULL | NULL | 19717 | 100.00 | NULL |
| 2 | DERIVED | u1 | NULL | ALL | NULL | NULL | NULL | NULL | 9818 | 100.00 | NULL |
| 3 | UNION | u2 | NULL | ALL | NULL | NULL | NULL | NULL | 9899 | 100.00 | NULL |
+----+-------------+------------+------------+------+---------------+------+---------+------+-------+----------+-------+
3 rows in set, 1 warning (0.00 sec)
-
MATERIALIZED
当执行子查询语句时,查询优化器先将子查询物化后再与外层查询进行连接查询时,其select_type
为MATERIALIZED
:
mysql> EXPLAIN SELECT * FROM u1 WHERE name IN (SELECT name from u2);
+----+--------------+-------------+------------+--------+---------------+------------+---------+------------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+--------------+-------------+------------+--------+---------------+------------+---------+------------+------+----------+-------------+
| 1 | SIMPLE | u1 | NULL | ALL | idx_name | NULL | NULL | NULL | 9818 | 100.00 | Using where |
| 1 | SIMPLE | | NULL | eq_ref | | | 303 | zz.u1.name | 1 | 100.00 | NULL |
| 2 | MATERIALIZED | u2 | NULL | index | idx_name | idx_name | 303 | NULL | 9899 | 100.00 | Using index |
+----+--------------+-------------+------------+--------+---------------+------------+---------+------------+------+----------+-------------+
3 rows in set, 1 warning (0.00 sec)
从执行计划可以看出,有两行id
为1
的记录,说明优化器将子查询转换成了连接查询,其id
为2
的行的select_type
为MATERIALIZED
,说明是先将子查询进行物化,然后再转换成了连接查询。
UNCACHEABLE SUBQUERY
略过。UNCACHEABLE UNION
略过。
partitions
查询记录中匹配的分区。对于非分区表,该值为NULL。
type
EXPLAIN
执行计划中的每一行都代表着对一张表的查询,而type
列则表示该表的访问方法。访问方法按从好到坏,依次为system
、const
,eq_ref
,ref
,fulltext
,ref_or_null
,index_merge
,unique_subquery
,index_subquery
,range
,index
,ALL
,下面分别来介绍一下。
-
system
如果表使用的引擎对于表行数统计是精确的(如:MyISAM),且表中只有一行记录的情况下,访问方法是system
。
先看看存储引擎为InnoDB
的表访问方法:
mysql> CREATE TABLE single_row (id INT NOT NULL) engine=InnoDB;
Query OK, 0 rows affected (0.02 sec)
mysql> INSERT INTO single_row VALUES(1);
Query OK, 1 row affected (0.01 sec)
mysql> EXPLAIN SELECT * FROM single_row;
+----+-------------+------------+------------+------+---------------+------+---------+------+------+----------+-------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+------------+------------+------+---------------+------+---------+------+------+----------+-------+
| 1 | SIMPLE | single_row | NULL | ALL | NULL | NULL | NULL | NULL | 1 | 100.00 | NULL |
+----+-------------+------------+------------+------+---------------+------+---------+------+------+----------+-------+
1 row in set, 1 warning (0.00 sec)
可以看到,对于InnoDB
引擎,type
类型为ALL
,换成MyISAM
引擎看看:
mysql> ALTER TABLE single_row engine=MyISAM;
Query OK, 1 row affected (0.03 sec)
Records: 1 Duplicates: 0 Warnings: 0
mysql> EXPLAIN SELECT * FROM single_row;
+----+-------------+------------+------------+--------+---------------+------+---------+------+------+----------+-------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+------------+------------+--------+---------------+------+---------+------+------+----------+-------+
| 1 | SIMPLE | single_row | NULL | system | NULL | NULL | NULL | NULL | 1 | 100.00 | NULL |
+----+-------------+------------+------------+--------+---------------+------+---------+------+------+----------+-------+
1 row in set, 1 warning (0.00 sec)
可以看到,改成MyISAM
引擎后,type
类型为system
。
-
const
当使用主键或唯一索引与常量作等值比较时(如果主键或唯一索引是联合索引的话,则联合索引的每一部分都必须是和常量做等值比较),就会使用const
,例如:
mysql> EXPLAIN SELECT * FROM u1 WHERE id=1000;
+----+-------------+-------+------------+-------+---------------+---------+---------+-------+------+----------+-------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+-------+---------------+---------+---------+-------+------+----------+-------+
| 1 | SIMPLE | u1 | NULL | const | PRIMARY | PRIMARY | 4 | const | 1 | 100.00 | NULL |
+----+-------------+-------+------------+-------+---------------+---------+---------+-------+------+----------+-------+
1 row in set, 1 warning (0.00 sec)
-
eq_ref
对于连接查询来说,如果被驱动表是使用主键或者非空唯一索引与常量进行等值比较(如果主键或非空唯一索引是联合索引,则联合索引的每一列都要做等值比较)的方法进行访问的,那么该驱动表访问方法为eq_ref
。
mysql> EXPLAIN SELECT * FROM u1, u2 WHERE u1.id=u2.id;
+----+-------------+-------+------------+--------+---------------+---------+---------+----------+------+----------+-------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+--------+---------------+---------+---------+----------+------+----------+-------+
| 1 | SIMPLE | u1 | NULL | ALL | PRIMARY | NULL | NULL | NULL | 9818 | 100.00 | NULL |
| 1 | SIMPLE | u2 | NULL | eq_ref | PRIMARY | PRIMARY | 4 | zz.u1.id | 1 | 100.00 | NULL |
+----+-------------+-------+------------+--------+---------------+---------+---------+----------+------+----------+-------+
2 rows in set, 1 warning (0.00 sec)
需要注意的是,如果唯一索引允许为NULL
,那么访问方法就不是eq_ref
,请看如下示例:
mysql> EXPLAIN SELECT * FROM u1, u2 WHERE u1.card_no=u2.card_no;
+----+-------------+-------+------------+------+---------------+-------------+---------+---------------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+------+---------------+-------------+---------+---------------+------+----------+-------------+
| 1 | SIMPLE | u1 | NULL | ALL | idx_card_no | NULL | NULL | NULL | 9818 | 100.00 | Using where |
| 1 | SIMPLE | u2 | NULL | ref | idx_card_no | idx_card_no | 5 | zz.u1.card_no | 1 | 100.00 | NULL |
+----+-------------+-------+------------+------+---------------+-------------+---------+---------------+------+----------+-------------+
2 rows in set, 1 warning (0.00 sec)
可以看到,在唯一索引允许为NULL
时,访问方法为ref
。
-
ref
当通过普通二级索引列与常量进行等值匹配来查询时,其访问方法有可能是ref
,ref
可用于使用=
或<=>
运算符进行比较的索引列。 在以下示例中,MySQL
可以使用ref
访问方法来处理查询:
mysql> EXPLAIN SELECT * FROM u1 WHERE name="abc";
+----+-------------+-------+------------+------+---------------+----------+---------+-------+------+----------+-------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+------+---------------+----------+---------+-------+------+----------+-------+
| 1 | SIMPLE | u1 | NULL | ref | idx_name | idx_name | 303 | const | 1 | 100.00 | NULL |
+----+-------------+-------+------------+------+---------------+----------+---------+-------+------+----------+-------+
1 row in set, 1 warning (0.00 sec)
mysql> EXPLAIN SELECT * FROM u1 WHERE name <=> "abc";
+----+-------------+-------+------------+------+---------------+----------+---------+-------+------+----------+-----------------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+------+---------------+----------+---------+-------+------+----------+-----------------------+
| 1 | SIMPLE | u1 | NULL | ref | idx_name | idx_name | 303 | const | 1 | 100.00 | Using index condition |
+----+-------------+-------+------------+------+---------------+----------+---------+-------+------+----------+-----------------------+
1 row in set, 1 warning (0.00 sec)
fulltext
连接使用了fulltext
索引。ref_or_null
这种连接类型类似于ref
,但是MySQL
会额外搜索包含NULL
值的行。 在以下示例中,MySQL可以使用ref_or_null
的访问方法来查询:
mysql> EXPLAIN SELECT * FROM u1 WHERE name = "abc" OR name IS NULL;
+----+-------------+-------+------------+-------------+---------------+----------+---------+-------+------+----------+-----------------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+-------------+---------------+----------+---------+-------+------+----------+-----------------------+
| 1 | SIMPLE | u1 | NULL | ref_or_null | idx_name | idx_name | 303 | const | 2 | 100.00 | Using index condition |
+----+-------------+-------+------------+-------------+---------------+----------+---------+-------+------+----------+-----------------------+
1 row in set, 1 warning (0.00 sec)
-
index_merge
查询优化器选择使用索引合并来优化查询。索引合并分为Intersection
、Union
、Sort-Union
三种。在使用索引合并的情况下,输出行中的key
列包含使用的索引列表,而key_len
包含使用的索引的长度(按长度最长的情况计算)列表。
mysql> EXPLAIN SELECT * FROM u1 WHERE name = "abc" OR school = "abc";
+----+-------------+-------+------------+-------------+---------------------+---------------------+---------+------+------+----------+-----------------------------------------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+-------------+---------------------+---------------------+---------+------+------+----------+-----------------------------------------------+
| 1 | SIMPLE | u1 | NULL | index_merge | idx_name,idx_school | idx_name,idx_school | 303,303 | NULL | 2 | 100.00 | Using union(idx_name,idx_school); Using where |
+----+-------------+-------+------------+-------------+---------------------+---------------------+---------+------+------+----------+-----------------------------------------------+
1 row in set, 1 warning (0.00 sec)
-
unique_subquery
类似于两表连接时,被驱动表的eq_ref
方法;unique_subquery
被用于IN
子查询中,如果IN
子查询中能够用主键索引做等值匹配进行查询的话,那么该子查询的访问方法为unique_subquery
。
mysql> EXPLAIN SELECT * FROM u1 WHERE id IN (SELECT id FROM u2 WHERE u1.card_no = u2.card_no) OR card_no = 1000;
+----+--------------------+-------+------------+-----------------+---------------------+---------+---------+------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+--------------------+-------+------------+-----------------+---------------------+---------+---------+------+------+----------+-------------+
| 1 | PRIMARY | u1 | NULL | ALL | idx_card_no | NULL | NULL | NULL | 9818 | 100.00 | Using where |
| 2 | DEPENDENT SUBQUERY | u2 | NULL | unique_subquery | PRIMARY,idx_card_no | PRIMARY | 4 | func | 1 | 10.00 | Using where |
+----+--------------------+-------+------------+-----------------+---------------------+---------+---------+------+------+----------+-------------+
2 rows in set, 2 warnings (0.00 sec)
可以看到,对u2
表的查询使用的是PRIMARY
主键索引,其type
列为unique_subquery
。
-
index_subquery
类似于unique_subquery
,只不过在子查询中用到的是非唯一索引:
mysql> EXPLAIN SELECT * FROM u1 WHERE school IN (SELECT school FROM u2 WHERE u1.name = u2.name) OR hoppy = "abc";
+----+--------------------+-------+------------+----------------+---------------------+------------+---------+------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+--------------------+-------+------------+----------------+---------------------+------------+---------+------+------+----------+-------------+
| 1 | PRIMARY | u1 | NULL | ALL | NULL | NULL | NULL | NULL | 9818 | 100.00 | Using where |
| 2 | DEPENDENT SUBQUERY | u2 | NULL | index_subquery | idx_name,idx_school | idx_school | 303 | func | 1 | 10.00 | Using where |
+----+--------------------+-------+------------+----------------+---------------------+------------+---------+------+------+----------+-------------+
2 rows in set, 2 warnings (0.00 sec)
-
range
当使用索引进行范围查询时,其访问类型为range
。
mysql> EXPLAIN SELECT * FROM u1 WHERE id > 1000;
+----+-------------+-------+------------+-------+---------------+---------+---------+------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+-------+---------------+---------+---------+------+------+----------+-------------+
| 1 | SIMPLE | u1 | NULL | range | PRIMARY | PRIMARY | 4 | NULL | 4909 | 100.00 | Using where |
+----+-------------+-------+------------+-------+---------------+---------+---------+------+------+----------+-------------+
1 row in set, 1 warning (0.01 sec)
mysql> EXPLAIN SELECT * FROM u1 WHERE name LIKE "abc%";
+----+-------------+-------+------------+-------+---------------+----------+---------+------+------+----------+-----------------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+-------+---------------+----------+---------+------+------+----------+-----------------------+
| 1 | SIMPLE | u1 | NULL | range | idx_name | idx_name | 303 | NULL | 1 | 100.00 | Using index condition |
+----+-------------+-------+------------+-------+---------------+----------+---------+------+------+----------+-----------------------+
1 row in set, 1 warning (0.00 sec)
-
index
如果查询中用的是覆盖索引,索引中的数据就可满足查询需要,此时的访问方法为index
。
mysql> EXPLAIN SELECT street FROM u1;
+----+-------------+-------+------------+-------+---------------+-------------+---------+------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+-------+---------------+-------------+---------+------+------+----------+-------------+
| 1 | SIMPLE | u1 | NULL | index | NULL | idx_address | 909 | NULL | 9818 | 100.00 | Using index |
+----+-------------+-------+------------+-------+---------------+-------------+---------+------+------+----------+-------------+
1 row in set, 1 warning (0.00 sec)
-
ALL
当使用全表扫描的方式进行查询时,访问方法为ALL
。 通常,可以通过添加索引来避免ALL
访问方法。
mysql> EXPLAIN SELECT * FROM u1;
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------+
| 1 | SIMPLE | u1 | NULL | ALL | NULL | NULL | NULL | NULL | 9818 | 100.00 | NULL |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------+
1 row in set, 1 warning (0.00 sec)
possible_keys
possible_keys
列表示MySQL
执行查询时可能用到的索引。如果这一列为NULL
,则没有使用到索引;这种情况下,需要检查WHERE
语句中所使用的的列,看是否可以通过给这些列中某个或多个添加索引的方法来提高查询性能。
mysql> EXPLAIN SELECT * FROM u1 WHERE name = "abc" AND school = "abc";
+----+-------------+-------+------------+------+---------------------+----------+---------+-------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+------+---------------------+----------+---------+-------+------+----------+-------------+
| 1 | SIMPLE | u1 | NULL | ref | idx_name,idx_school | idx_name | 303 | const | 1 | 5.00 | Using where |
+----+-------------+-------+------------+------+---------------------+----------+---------+-------+------+----------+-------------+
1 row in set, 1 warning (0.00 sec)
可以看到,possible_keys
列显示了两种可用的索引idx_name
和idx_school
,从key
列可以看到,实际使用的是idx_name
索引。
需要注意的一点是,当type
为index
时,会出现possible_keys
为NULL
而key
列为实际使用到的索引的情况,如下所示:
mysql> EXPLAIN SELECT city FROM u1;
+----+-------------+-------+------------+-------+---------------+-------------+---------+------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+-------+---------------+-------------+---------+------+------+----------+-------------+
| 1 | SIMPLE | u1 | NULL | index | NULL | idx_address | 909 | NULL | 9818 | 100.00 | Using index |
+----+-------------+-------+------------+-------+---------------+-------------+---------+------+------+----------+-------------+
1 row in set, 1 warning (0.00 sec)
key
key
列表示MySQL
实际使用到的索引。
当使用覆盖索引时,key
有可能会命中possible_keys
中不存在的索引。key_len
key_len
列表示MySQL
实际使用的索引的最大长度;当使用到联合索引时,有可能是多个列的长度和。
索引长度由三个部分组成:
1、索引本身字节数
对于固定长度类型的索引列来说,就是它实际占用的存储空间的长度,比如说int
类型占用4
个字节;对于可变长度类型来说,是它实际占用的存储空间的最大长度,比如说varchar(100)
类型,如果编码类型为utf8
,那么该长度为100*3=300
个字节,如果编码类型为utf8mb4
,那么该长度为100*4=400
个字节
2、如果该索引列允许为NULL
值,则key_len
再加1
个字节。
3、如果是变长字段,则key_len
再加2
个字节。
示例如下:
mysql> EXPLAIN SELECT * FROM u1 WHERE name = "abc";
+----+-------------+-------+------------+------+---------------+----------+---------+-------+------+----------+-------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+------+---------------+----------+---------+-------+------+----------+-------+
| 1 | SIMPLE | u1 | NULL | ref | idx_name | idx_name | 303 | const | 1 | 100.00 | NULL |
+----+-------------+-------+------------+------+---------------+----------+---------+-------+------+----------+-------+
1 row in set, 1 warning (0.00 sec)
由于name
列为varchar(100)
类型,编码格式为utf8
并且允许为NULL
,则计算公式如下:
100 * 3 + 1 + 2 = 303
再来看一下固定长度索引的示例:
mysql> EXPLAIN SELECT * FROM u1 WHERE id = 1000;
+----+-------------+-------+------------+-------+---------------+---------+---------+-------+------+----------+-------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+-------+---------------+---------+---------+-------+------+----------+-------+
| 1 | SIMPLE | u1 | NULL | const | PRIMARY | PRIMARY | 4 | const | 1 | 100.00 | NULL |
+----+-------------+-------+------------+-------+---------------+---------+---------+-------+------+----------+-------+
1 row in set, 1 warning (0.00 sec)
上例中使用到了主键索引,int
类型且不允许为NULL
,索引key_len
列显示为4
。
- ref
ref
列显示的是,通过索引列做等值比较查询时(也就是在访问方法是const
、eq_ref
、ref
、ref_or_null
、unique_subquery
、index_subquery
其中之一时),哪些列或常量被用来与key
列中的索引做比较。
示例如下:
mysql> EXPLAIN SELECT * FROM u1 WHERE id = 1000;
+----+-------------+-------+------------+-------+---------------+---------+---------+-------+------+----------+-------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+-------+---------------+---------+---------+-------+------+----------+-------+
| 1 | SIMPLE | u1 | NULL | const | PRIMARY | PRIMARY | 4 | const | 1 | 100.00 | NULL |
+----+-------------+-------+------------+-------+---------------+---------+---------+-------+------+----------+-------+
1 row in set, 1 warning (0.00 sec)
mysql> EXPLAIN SELECT * FROM u1, u2 WHERE u1.name = u2.name;
+----+-------------+-------+------------+------+---------------+----------+---------+------------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+------+---------------+----------+---------+------------+------+----------+-------------+
| 1 | SIMPLE | u2 | NULL | ALL | idx_name | NULL | NULL | NULL | 9899 | 100.00 | Using where |
| 1 | SIMPLE | u1 | NULL | ref | idx_name | idx_name | 303 | zz.u2.name | 1 | 100.00 | NULL |
+----+-------------+-------+------------+------+---------------+----------+---------+------------+------+----------+-------------+
2 rows in set, 1 warning (0.00 sec)
如果显示为func
, 则表示使用到的值是某个函数的结果。
mysql> EXPLAIN SELECT * FROM u1, u2 WHERE u1.name = SUBSTRING(u2.name, 1, 10);
+----+-------------+-------+------------+------+---------------+----------+---------+------+------+----------+-----------------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+------+---------------+----------+---------+------+------+----------+-----------------------+
| 1 | SIMPLE | u2 | NULL | ALL | NULL | NULL | NULL | NULL | 9899 | 100.00 | NULL |
| 1 | SIMPLE | u1 | NULL | ref | idx_name | idx_name | 303 | func | 1 | 100.00 | Using index condition |
+----+-------------+-------+------------+------+---------------+----------+---------+------+------+----------+-----------------------+
2 rows in set, 1 warning (0.00 sec)
rows
rows
列表示MySQL
认为执行查询时所需要检查的行数。对于InnoDB
表,这个值为估计值,并不完全准确。filtered
filtered
列表示被查询条件过滤后的留存百分比。最大值为100,意味着实际并没有过滤掉任何行。此值从100逐渐降低时,表示被过滤掉的行数越来越多。
rows
列表示MySQL
要检查的行数的估计值,rows × filtered
表示过滤后,要和下张表进行join
的行数。
mysql> EXPLAIN SELECT * FROM u1, u2 WHERE u1.name = u2.name AND u1.hoppy = "abc";
+----+-------------+-------+------------+------+---------------+----------+---------+------------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+------+---------------+----------+---------+------------+------+----------+-------------+
| 1 | SIMPLE | u1 | NULL | ALL | idx_name | NULL | NULL | NULL | 9818 | 10.00 | Using where |
| 1 | SIMPLE | u2 | NULL | ref | idx_name | idx_name | 303 | zz.u1.name | 1 | 100.00 | NULL |
+----+-------------+-------+------------+------+---------------+----------+---------+------------+------+----------+-------------+
2 rows in set, 1 warning (0.00 sec)
从执行计划可以看出,u1
为驱动表,u2
为被驱动表。驱动表预计扫描9818
行记录,有10%
的记录会被保留下来,也就是说驱动表大概有9818 * 10% = 981.8
条记录被用来u2
表做连接,也就是说被驱动表将会被执行约982
次。
Extra
这列包含了MySQL
解析查询的额外信息,通过这些信息,可以更准确的理解MySQL
到底是如何执行查询的。
Extra
列信息比较多,我们挑一些常用的来讲解一下。
-
Using index
在使用了覆盖索引时,Extra
列会显示Using index
信息。
如下所示:
mysql> EXPLAIN SELECT city, street FROM u1;
+----+-------------+-------+------------+-------+---------------+-------------+---------+------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+-------+---------------+-------------+---------+------+------+----------+-------------+
| 1 | SIMPLE | u1 | NULL | index | NULL | idx_address | 909 | NULL | 9818 | 100.00 | Using index |
+----+-------------+-------+------------+-------+---------------+-------------+---------+------+------+----------+-------------+
1 row in set, 1 warning (0.00 sec)
-
Using index condition
索引条件下推的详细内容我在单独的一篇文章里介绍过了,不熟悉的小伙伴可以看这里:一文读懂什么是MySQL索引下推(ICP)
如果查询优化器选择使用索引条件下推这个特性,在Extra列中将会显示Using index condition
,比如下面的查询:
mysql> EXPLAIN SELECT * FROM u1 WHERE city = "abc" AND district LIKE "%abc";
+----+-------------+-------+------------+------+---------------+-------------+---------+-------+------+----------+-----------------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+------+---------------+-------------+---------+-------+------+----------+-----------------------+
| 1 | SIMPLE | u1 | NULL | ref | idx_address | idx_address | 303 | const | 1 | 11.11 | Using index condition |
+----+-------------+-------+------------+------+---------------+-------------+---------+-------+------+----------+-----------------------+
1 row in set, 1 warning (0.00 sec)
-
Using where
如果查询没有用到索引(也就是全表扫描),那么当查询语句的WHERE
条件中有针对该表的列搜索时,Extra
列会显示Using where
。请看下面的示例:
mysql> EXPLAIN SELECT * FROM u1 WHERE hoppy = 'abc';
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+
| 1 | SIMPLE | u1 | NULL | ALL | NULL | NULL | NULL | NULL | 9818 | 10.00 | Using where |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+
1 row in set, 1 warning (0.00 sec)
如果查询使用到了索引,那么当WHERE
条件中包含该索引之外的列时,Extra
列会显示Using where
。请看下面的示例:
mysql> EXPLAIN SELECT * FROM u1 WHERE name = "abc" AND hoppy = "abc";
+----+-------------+-------+------------+------+---------------+----------+---------+-------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+------+---------------+----------+---------+-------+------+----------+-------------+
| 1 | SIMPLE | u1 | NULL | ref | idx_name | idx_name | 303 | const | 1 | 10.00 | Using where |
+----+-------------+-------+------------+------+---------------+----------+---------+-------+------+----------+-------------+
1 row in set, 1 warning (0.00 sec)
-
Using join buffer (Block Nested Loop)
在连接查询执行过程中,如果被驱动表不能有效的利用索引加快查询速度,MySQL
一般会为其分配一块名叫join buffer
的内存块来加快查询速度,也就是基于块的嵌套循环算法
,比如下面这个查询:
mysql> EXPLAIN SELECT * FROM u1, u2 WHERE u1.hoppy = u2.hoppy;
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+----------------------------------------------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+----------------------------------------------------+
| 1 | SIMPLE | u1 | NULL | ALL | NULL | NULL | NULL | NULL | 9818 | 100.00 | NULL |
| 1 | SIMPLE | u2 | NULL | ALL | NULL | NULL | NULL | NULL | 9899 | 10.00 | Using where; Using join buffer (Block Nested Loop) |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+----------------------------------------------------+
2 rows in set, 1 warning (0.00 sec)
-
Using sort_union(...), Using union(...), Using intersect(...)
如果执行计划的Extra
列出现了Using intersect(...)
提示,说明准备使用Intersect
索引合并的方式执行查询,括号中的...
表示需要进行索引合并的索引名称;如果出现了Using union(...)
提示,说明准备使用Union
索引合并的方式执行查询;出现了Using sort_union(...)
提示,说明准备使用Sort-Union
索引合并的方式执行查询。
比如这个查询的执行计划,使用到了Intersect
索引合并:
mysql> EXPLAIN SELECT * FROM u1 WHERE name = "a" AND school = "b";
+----+-------------+-------+------------+-------------+---------------------+---------------------+---------+------+------+----------+---------------------------------------------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+-------------+---------------------+---------------------+---------+------+------+----------+---------------------------------------------------+
| 1 | SIMPLE | u1 | NULL | index_merge | idx_name,idx_school | idx_name,idx_school | 303,303 | NULL | 1 | 100.00 | Using intersect(idx_name,idx_school); Using where |
+----+-------------+-------+------------+-------------+---------------------+---------------------+---------+------+------+----------+---------------------------------------------------+
1 row in set, 1 warning (0.00 sec)
再比如下面这个查询的执行计划,使用到了Union
索引合并:
mysql> EXPLAIN SELECT * FROM u1 WHERE name = "a" OR school = "b";
+----+-------------+-------+------------+-------------+---------------------+---------------------+---------+------+------+----------+-----------------------------------------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+-------------+---------------------+---------------------+---------+------+------+----------+-----------------------------------------------+
| 1 | SIMPLE | u1 | NULL | index_merge | idx_name,idx_school | idx_name,idx_school | 303,303 | NULL | 8 | 100.00 | Using union(idx_name,idx_school); Using where |
+----+-------------+-------+------------+-------------+---------------------+---------------------+---------+------+------+----------+-----------------------------------------------+
1 row in set, 1 warning (0.00 sec)
下面这个查询语句,使用到了Sort-Union
索引合并:
mysql> EXPLAIN SELECT * FROM u1 WHERE name < "a" OR school < "b";
+----+-------------+-------+------------+-------------+---------------------+---------------------+---------+------+------+----------+----------------------------------------------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+-------------+---------------------+---------------------+---------+------+------+----------+----------------------------------------------------+
| 1 | SIMPLE | u1 | NULL | index_merge | idx_name,idx_school | idx_name,idx_school | 303,303 | NULL | 395 | 100.00 | Using sort_union(idx_name,idx_school); Using where |
+----+-------------+-------+------------+-------------+---------------------+---------------------+---------+------+------+----------+----------------------------------------------------+
1 row in set, 1 warning (0.00 sec)
-
Using filesort
有一些情况下对结果集中的记录进行排序时可以使用到索引,但是在很多情况下,无法使用到索引,只能在内存(数据较少)或磁盘(数据较大)中进行排序。这种在内存中或者磁盘上进行排序的方式统称为文件排序(英文名:filesort
)。如果某个查询需要使用文件排序的方式执行查询,就会在执行计划的Extra
列中显示Using filesort
提示,比如下面这条语句:
mysql> EXPLAIN SELECT * FROM u1 ORDER BY hoppy;
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+----------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+----------------+
| 1 | SIMPLE | u1 | NULL | ALL | NULL | NULL | NULL | NULL | 9818 | 100.00 | Using filesort |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+----------------+
1 row in set, 1 warning (0.00 sec)
:通常来讲,出现文件排序会使查询性能变差,应该
尽量避免
,尽量将文件排序
提升为索引排序
。
-
Using temporary
在许多查询的执行过程中,MySQL
可能会借助临时表来完成一些功能,比如去重、排序之类的。比如我们在执行许多包含DISTINCT
、GROUP BY
、UNION
等子句的查询过程中,如果不能有效利用索引来完成查询,MySQL
很有可能寻求通过建立内部的临时表来执行查询。如果查询中使用到了内部的临时表,在执行计划的Extra
列将会显示Using temporary
提示,比如下面的语句:
mysql> EXPLAIN SELECT hoppy, count(*) as cnt FROM u1 GROUP BY hoppy;
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+---------------------------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+---------------------------------+
| 1 | SIMPLE | u1 | NULL | ALL | NULL | NULL | NULL | NULL | 9818 | 100.00 | Using temporary; Using filesort |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+---------------------------------+
1 row in set, 1 warning (0.00 sec)
可以看到,上述执行计划的Extra
列不仅仅包含Using temporary
提示,还包含Using filesort
提示,可是我们的查询语句中明明没有写ORDER BY
子句呀?这是因为MySQL
会在包含GROUP BY
子句的查询中默认添加上ORDER BY
子句。如果我们并不想为包含GROUP BY
子句的查询进行排序,需要我们显式的写上ORDER BY NULL
,就像这样:
mysql> EXPLAIN SELECT hoppy, count(*) as cnt FROM u1 GROUP BY hoppy ORDER BY NULL;
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-----------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-----------------+
| 1 | SIMPLE | u1 | NULL | ALL | NULL | NULL | NULL | NULL | 9818 | 100.00 | Using temporary |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-----------------+
1 row in set, 1 warning (0.00 sec)
可见,Extra
列里没有出现Using filesort
提示了,这就节约了排序成本,提升了查询效率。
:与
Using filesort
类似,Extra
列出现Using temporary
也不是什么好事,应该尽量避免。
参考与感谢:
1、《MySQL 是怎样运行的:从根儿上理解 MySQL》
2、MySQL官方手册