目录
Explain简介
Explain 分析
1、id列
2、select_type 列
3、type 连接类型
4、possible_key、key 列
5、key_len列
6、rows 列
7、filtered 列
8、ref 列
9、Extra 列
SQL脚本
MySQL提供了一个执行计划的工具,在MySQL架构中,SQL语句通过优化器最终生成的就是一个执行计划。通过EXPLAIN我们可以模拟优化器执行SQL查询语句的过程,来知道MySQL是怎么处理一条SQL语句的。通EXPLAIN我们可以分析语句或者表的性能瓶颈,然后进行优化。
官方地址:
MySQL :: MySQL 5.7 Reference Manual :: 8.8.2 EXPLAIN Output Format
我们新建三张表,三张表只有主键索引,来详细讲解一下EXPLAIN。(建表SQL在文章末尾)。
mysql> EXPLAIN SELECT * FROM book;
+----+-----------+--------+----------+-----+-------------+-----+-------+----+----+--------+------+
| id |select_type|table |partitions| type|possible_keys| key |key_len|ref |rows|filtered| Extra|
+----+-----------+--------+----------+-----+-------------+-----+-------+----+----+--------+------+
| 1 |SIMPLE |book |NULL | ALL |NULL | NULL| NULL |NULL| 4 | 100.00 | NULL |
+----+-----------+--------+----------+-----+-------------+-----+-------+----+----+--------+------+
1 row in set, 1 warning (0.00 sec)
接下来我们将分析图中 explain 中每列的信息。
id列的编号是 select 的序列号,有几个 select 就有几个id,并且id的顺序是按 select 出现的顺序增长的。MySQL将 select 查询分为简单查询和复杂查询。复杂查询分为三类:简单子查询、派生表(from语句中的子查询)、union 查询。
select id是SQL执行的顺序的标识,SQL从大到小的执行
1)id 值不同,id 值越大优先级越高,越先被执行
我们查询book表name=java的email信息
EXPLAIN
SELECT
email
FROM
contact
WHERE id = (
SELECT contact_id FROM author
WHERE id = (
SELECT author_id FROM book WHERE NAME = 'java'
)
);
我们先根据name=java从book表中获取author_id,再根据author_id从author表中获取contact_id,根据contact_id查询contact表获取email。
上面SQL语句执行的顺序为book —> author —> contact,在执行计划中,id依次为 3、2、1
2)id值相同,由上到下顺序执行
我们获取book表id=1和author表contact_id =2的数据
EXPLAIN
SELECT
book.name,author.name,email
FROM
book,author,contact
WHERE
book.author_id = author.id
AND author.contact_id = contact.id
AND (book.id = 1 or contact.id = 2)
我们从执行计划中,可以看到id值一样,book表在最后,执行的顺序为:book(4条)—> author (3条)—> contact (3条)
我们往author表中新增2条数据,再次执行上面的执行计划
INSERT INTO `author`(`id`, `name`, `contact_id`) VALUES (4, '张一', 4);
INSERT INTO `author`(`id`, `name`, `contact_id`) VALUES (5, '李二', 5);
EXPLAIN
SELECT
book.name,author.name,email
FROM
book,author,contact
WHERE
book.author_id = author.id
AND author.contact_id = contact.id
AND (book.id = 1 or contact.id = 2)
此时执行的顺序为:contact (3条)—> author (5条)—> book(4条)
随着表数据的改变,执行顺序也会跟着改变,
结论:
id值不同,先大后小,id 值相同时,从上往下顺序执行。
select_type :查询类型,表示对应行是是简单还是复杂的查询,常见的查询类型:
1)simple:简单查询
SIMPLE 简单查询,不包含子查询,不包含关联查询 union
EXPLAIN
SELECT * FROM book;
2)primary:复杂查询中最外层的 select
PRIMARY 子查询 SQL 语句中的主查询,也就是最外面的那层查询。
EXPLAIN
SELECT * FROM author
WHERE id = (
SELECT author_id FROM book WHERE NAME = 'java'
);
3)subquery:包含在 select 中的子查询(不在 from 子句中)
SUBQUERY 子查询中所有的内层查询都是 SUBQUERY 类型的。
EXPLAIN
SELECT * FROM author
WHERE id = (
SELECT author_id FROM book WHERE NAME = 'java'
);
4)derived:衍生查询
derived 查询包含在 from 子句中的子查询。MySQL会将结果存放在一个临时表中,也称为派生表(derived的英文含义)
EXPLAIN SELECT * FROM (SELECT max(price) FROM book) tab;
5)union:联合查询
union查询在 union 中的第二个和随后的 select
6)union result:
从 union 临时表检索结果的 select
EXPLAIN SELECT * FROM book WHERE id=1 UNION SELECT * FROM book WHERE id=2
EXPLAIN输出的type列描述了表是如何连接的,从最佳类型到最差类型排序:
system > const > eq_ref > ref > fulltext > ref_or_null > index_merge > unique_subquery > range > index > all
以上访问类型除了 all,都能用到索引。
type 所有的连接类型中,常用的连接类型:system、const 、eq_ref、ref、range、index、all。
1)const
主键索引或者唯一索引,只能查到一条数据的 SQL。
EXPLAIN SELECT * FROM book WHERE id=1;
2)system
system 是 const 的一种特例,官网上的说明:
The table has only one row (= system table). This is a special case of the const join type.
只有一行满足条件。例如:只有一条数据的系统表。
EXPLAIN SELECT * FROM mysql.proxies_priv;
注意:
proxies_priv表的存储引擎是MyISAM,我们新建一张表,存储引擎为MyISAM,表里面只有一条数据,type结果也是system,将存储引擎修改为InnoDB,type的结果就会变为const。
3)eq_ref
eq_ref 通常出现在多表的 join 查询,表示对于前表的每一个结果,,都只能匹配到后表的一行结果。一般是唯一性索引的查询(UNIQUE 或 PRIMARY KEY)。 eq_ref 是除 const 之外最好的访问类型。
EXPLAIN SELECT * FROM book,author WHERE book.author_id=author.id;
4)ref
ref 查询用到了非唯一性索引,或者关联操作只使用了索引的最左前缀。
我们先在book表nam字段上建立一个索引
ALTER TABLE book ADD INDEX `idx_name`(`name`);
根据name 非唯一性索引进行查询
EXPLAIN SELECT * FROM book WHERE name = 'java';
关联操作只使用了索引的最左前缀,我们将author表的主键字段,由id修改为联合主键(id,name)
EXPLAIN SELECT * FROM book,author WHERE book.author_id=author.id;
当author表id字段为主键的时候,关联查询type为eq_ref,当建立联合索引后,关联查询,id字段使用了最左匹配原则,使用了(id,name)联合索引,type此时为ref。
每小节我们执行完修改表操作,我们都执行rollback操作,保持表最原先的结构,方便后续内容的讲解,后面就不再说明。
rollback;
5)range
我们在book表重新建立了name索引,此时我们执行模糊查询
ALTER TABLE book ADD INDEX `idx_name`(`name`);
EXPLAIN SELECT * FROM book WHERE name like 'java';
EXPLAIN SELECT * FROM book WHERE id BETWEEN 2 AND 4;
range 索引范围扫描。 如果 where 后面是 like、between and 、 >= 、<=、in 这些,type 类型就为 range。
6)all
不走索引一定是全表扫描ALL。
EXPLAIN SELECT * FROM book WHERE price > 60;
possible_key是可能用到的索引,key实际用到的索引,如果key为NULL 就表示没有用到索引。 possible_key 可以有一个或者多个,可能用到索引不代表一定用到索引。 反过来,possible_key 为空,key 可能有值吗?
我们在book表上建立2个索引,一个是name,一个是(name,price)联合索引。
ALTER TABLE book ADD INDEX `idx_name`(`name`);
ALTER TABLE book ADD INDEX `idx_name_price`(`name`, `price`);
我们查询name=java的书籍,发现有2个索引,idx_name和idx_name_price,最终使用了idx_name索引,
EXPLAIN SELECT * FROM book WHERE name = 'java';
我们再来查询 author_id = 1 的数据,由于author_id 没有索引,所以possible_key和key都为Null,走全表扫描。
EXPLAIN SELECT * FROM book WHERE author_id = 1;
key_len是查询使用到索引的长度,跟索引字段的类型、长度有关。
详细的key_len计算可以看《Explain执行计划key_len详解》这篇博客。
我们在book表name和author字段分表建立索引
ALTER TABLE book ADD INDEX `idx_name`(`name`);
ALTER TABLE book ADD INDEX `idx_author_id`(`author_id`);
我们根据name索引进行查询,name字段类型为varchar(255),允许为空,CHARSET=utf8mb4 ,key_ken = 4 * 255 + 2 + 1 = 1023 ;
EXPLAIN SELECT * FROM book WHERE name = 'java';
我们根据author_id 索引进行查询,author_id 字段类型为int,允许为null ,key_len = 4 + 1 = 5;
EXPLAIN SELECT * FROM book WHERE author_id = 1;
MySQL认为扫描多少行才能返回请求的数据,是一个预估值,rows的值一般来说行数越小越好。
EXPLAIN
SELECT
*
FROM
book,author
WHERE
book.author_id = author.id and author.name ='张三';
filtered表示存储引擎返回的数据在 server 层过滤后,剩下多少满足查询的记录数量的比例,它是一个百分比,在author表中有三条数据,我们查询name=张三的记录,只有一条,也就是33.33%符合查询结果。
EXPLAIN
SELECT
*
FROM
book,author
WHERE
book.author_id = author.id and author.name ='张三';
ref使用哪个列或者常数和索引一起从表中筛选数据。
EXPLAIN
SELECT
*
FROM
author,contact
WHERE
contact.id = author.contact_id and name ='李四';
Extra执行计划给出的额外的信息说明,可以根据Extra来进行优化。
1)Using index
Using index 用到了覆盖索引,不需要回表。
ALTER TABLE book ADD INDEX `idx_name_price`(`name`, `price`);
EXPLAIN SELECT name,price FROM book WHERE name = 'java';
2)Using where
Using where 使用了 where 过滤,表示存储引擎返回的记录并不是所有的都满足查询条件,需要 在 server 层进行过滤(跟是否使用索引没有关系)。
EXPLAIN SELECT * FROM book WHERE author_id = 1;
3)Using index condition
Using index condition索引条件下推
ALTER TABLE book ADD INDEX `idx_name`(`name`);
EXPLAIN SELECT * FROM book WHERE name like 'j%';
https://dev.mysql.com/doc/refman/5.7/en/index-condition-pushdown-optimization.html
4)Using filesort
Using filesort 不能使用索引来排序,用到了额外的排序。这种情况下一般也是要考虑使用索引来优化的。
EXPLAIN SELECT * FROM book order by price;
5)Using temporary
Using temporary 表示用到了临时表。mysql需要创建一张临时表来处理查询。出现这种情况一般是要进行优化的。
EXPLAIN SELECT DISTINCT(author_id) FROM book;
CREATE TABLE `author` (
`id` int(11) NOT NULL,
`name` varchar(255) DEFAULT NULL,
`contact_id` int(11) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE `book` (
`id` int(11) NOT NULL,
`name` varchar(255) DEFAULT NULL,
`price` decimal(10,2) DEFAULT NULL,
`author_id` int(11) DEFAULT NULL,
PRIMARY KEY (`id`),
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE `contact` (
`id` int(11) NOT NULL,
`email` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
INSERT INTO book(`id`, `name`, `price`, `author_id`) VALUES (1, 'java', 99.00, 1);
INSERT INTO book(`id`, `name`, `price`, `author_id`) VALUES (2, 'spring', 80.00, 1);
INSERT INTO book(`id`, `name`, `price`, `author_id`) VALUES (3, 'jvm', 109.00, 2);
INSERT INTO book(`id`, `name`, `price`, `author_id`) VALUES (4, 'mybatis', 66.00, 3);
INSERT INTO author(`id`, `name`, `contact_id`) VALUES (1, '张三', 1);
INSERT INTO author(`id`, `name`, `contact_id`) VALUES (2, '李四', 2);
INSERT INTO author(`id`, `name`, `contact_id`) VALUES (3, '王五', 3);
INSERT INTO contact(`id`, `email`) VALUES (1, '[email protected]');
INSERT INTO contact(`id`, `email`) VALUES (2, '[email protected]');
INSERT INTO contact(`id`, `email`) VALUES (3, '[email protected]');