explain语句结果:
字段解释:
sql准备:
USE atguigudb;
CREATE TABLE t1(id INT(10) AUTO_INCREMENT, content VARCHAR(100) NULL, PRIMARY KEY (id));
CREATE TABLE t2(id INT(10) AUTO_INCREMENT, content VARCHAR(100) NULL, PRIMARY KEY (id));
CREATE TABLE t3(id INT(10) AUTO_INCREMENT, content VARCHAR(100) NULL, PRIMARY KEY (id));
CREATE TABLE t4(id INT(10) AUTO_INCREMENT, content1 VARCHAR(100) NULL, content2 VARCHAR(100) NULL, PRIMARY KEY (id));
CREATE INDEX idx_content1 ON t4(content1); -- 普通索引
# 以下新增sql多执行几次,以便演示
INSERT INTO t1(content) VALUES(CONCAT('t1_',FLOOR(1+RAND()*1000)));
INSERT INTO t2(content) VALUES(CONCAT('t2_',FLOOR(1+RAND()*1000)));
INSERT INTO t3(content) VALUES(CONCAT('t3_',FLOOR(1+RAND()*1000)));
INSERT INTO t4(content1, content2) VALUES(CONCAT('t4_',FLOOR(1+RAND()*1000)), CONCAT('t4_',FLOOR(1+RAND()*1000)));
其中,较为重要的指标有:type、possible_keys、keys、rows、extra
1. id
id的情况有三种:
1.1 id相同
表示加载表的顺序是从上到下
EXPLAIN SELECT * FROM t1, t2, t3;
1.2 id不同
如果是子查询,id的序号会递增,id值越大优先级越高,越先被执行
EXPLAIN SELECT t1.id FROM t1 WHERE t1.id =(
SELECT t2.id FROM t2 WHERE t2.id =(
SELECT t3.id FROM t3 WHERE t3.content = 't3_434'
)
);
注意:查询优化器可能对涉及子查询的语句进行优化,转为连接查询
EXPLAIN SELECT * FROM t1 WHERE content IN
(SELECT content FROM t2 WHERE content = 'a');
1.3 id有相同,也有不同,同时存在
id相同可以认为是一组,从上往下顺序执行;在所有组中,id的值越大,优先级越高,越先执行
1.4 id为null
最后执行
EXPLAIN SELECT * FROM t1 UNION SELECT * FROM t2;
1.5 总结
- id如果相同,可以认为是一组,从上往下顺序执行
- 在所有组中,id值越大,优先级越高,越先执行
- 关注点:id号每个号码,表示一趟独立的查询, 一个sql的查询趟数越少越好
2. select_type
2.1 simple:简单查询
EXPLAIN SELECT * FROM t1;
2.2 primary:主查询
即子查询中最外层查询
2.3 subquery :子查询
在SELECT或WHERE列表中包含了子查询。
EXPLAIN SELECT * FROM t3 WHERE id =
( SELECT id FROM t2 WHERE content= 'a');
2.4 dependent subquery:
如果包含了子查询,并且查询语句不能被优化器转换为连接查询,并且子查询是相关子查询(子查询基于外部数据列)
,则子查询就是DEPENDENT SUBQUREY。
注意:出现此类型,坚决要修改sql语句,这个太慢了!!!
最好是修改为derived的形式。
这个意思是,自己是个子查询,并且还依赖于外层的查询结果。
EXPLAIN SELECT * FROM t3 WHERE id =
( SELECT id FROM t2 WHERE content = t3.content);
详解看:https://blog.csdn.net/joenqc/article/details/73189143
2.5 UNCACHEABLE SUBQUREY:
表示这个subquery的查询要受到外部系统变量的影响
EXPLAIN SELECT * FROM t3
WHERE id = ( SELECT id FROM t2 WHERE content = @@character_set_server);
2.6 derived:临时表:from中子查询产生的临时表
在包含派生表(子查询在from子句中)
的查询中,MySQL会递归执行这些子查询,把结果放在临时表里。
EXPLAIN SELECT * FROM (
SELECT content, COUNT(*) AS c FROM t1 GROUP BY content) AS derived_t1
WHERE c > 1;
这里的<derived2>
就是在id为2的查询中产生的派生表。
补充:MySQL在处理带有派生表的语句时,优先尝试把派生表和外层查询进行合并,如果不行,再把派生表物化掉(执行子查询,并把结果放入临时表)
,然后执行查询。下面的例子就是就是将派生表和外层查询进行合并的例子:
EXPLAIN SELECT * FROM (SELECT * FROM t1 WHERE content = 't1_832') AS derived_t1;
下面不加limit也会被合并:
2.7 union及union result:
- UNION:对于包含UNION或者UNION ALL的查询语句,除了最左边的查询是PRIMARY,其余的查询都是UNION。
- UNION RESULT:UNION会对查询结果进行查询去重,MYSQL会使用临时表来完成UNION查询的去重工作,针对这个临时表的查询就是"UNION RESULT"。
EXPLAIN
SELECT * FROM t3 WHERE id = 1
UNION
SELECT * FROM t2 WHERE id = 1;
2.8 DEPENDENT UNION:
子查询中的UNION或者UNION ALL,除了最左边的查询是DEPENDENT SUBQUREY,其余的查询都是DEPENDENT UNION。
EXPLAIN SELECT * FROM t1 WHERE content IN
(
SELECT content FROM t2
UNION
SELECT content FROM t3
);
2.9 MATERIALIZED:
优化器对于包含子查询的语句,如果选择将子查询物化后再与外层查询连接查询
,该子查询的类型就是MATERIALIZED。如下的例子中,查询优化器先将子查询转换成物化表,然后将t1和物化表进行连接查询。
EXPLAIN SELECT * FROM t1 WHERE content IN (SELECT content FROM t2);
3. type:访问类型 ☆
说明:
结果值从最好到最坏依次是:
system > const > eq_ref > ref
> fulltext > ref_or_null > index_merge > unique_subquery > index_subquery > range > index > ALL
比较重要的包含:system > const > eq_ref > ref > range > index > ALL
SQL 性能优化的目标:至少要达到 range
级别,要求是 ref
级别,最好是 consts
级别。(阿里巴巴开发手册要求)
3.1 NULL
3.2 system
查询系统表专用,5.7 以上版本不再显示system直接显示all,不多解释
3.3 const:命中主键索引
根据主键
或者唯一二级索引
列与常数
进行匹配时
EXPLAIN SELECT * FROM t1 WHERE id = 1;
3.4 eq_ref:唯一索引和主键索引相互关联
连接查询时通过主键或不允许NULL值的唯一二级索引列进行等值匹配时(主要用在一对一的查询)
EXPLAIN SELECT * FROM t1, t2 WHERE t1.id = t2.id;
3.5 ref:命中普通索引
通过普通二级索引列与常量进行等值匹配时(命中了普通索引)
EXPLAIN SELECT * FROM t_emp WHERE deptId = 1;
3.6 range:主键索引范围查询
只检索给定范围的行,使用一个索引来选择行。key 列显示使用了哪个索引,一般就是在你的where语句中出现了between、<、>、in等的查询。这种范围扫描索引扫描比全表扫描要好,因为它只需要开始于索引的某一点,而结束于另一点,不用扫描全部索引
EXPLAIN SELECT * FROM t1 WHERE id IN (1, 2, 3);
3.7 index:普通索引全表扫描
当使用覆盖索引
,但需要扫描全部的索引记录时
覆盖索引:
如果能通过读取索引就可以得到想要的数据,那就不需要读取用户记录,或者不用再做回表操作了。一个索引包含了满足查询结果的数据就叫做覆盖索引。
-- 只需要读取聚簇索引部分的非叶子节点,就可以得到id的值,不需要查询叶子节点
EXPLAIN SELECT id FROM t1;
-- 只需要读取二级索引,就可以在二级索引中获取到想要的数据,
-- 不需要再根据叶子节点中的id做回表操作
EXPLAIN SELECT id, deptId FROM t_emp;
3.8 all:全表扫描
全表扫描。Full Table Scan,将遍历全表以找到匹配的行
EXPLAIN SELECT * FROM t1;
3.9 其他类型:(了解)
- index_subquery:利用
普通索引
来关联子查询,针对包含有IN子查询的查询语句。content1是普通索引字段
EXPLAIN SELECT * FROM t1 WHERE content IN (SELECT content1 FROM t4 WHERE t1.content = t4.content2) OR content = 'a';
- unique_subquery:类似于index_subquery,利用
唯一索引
来关联子查询。t2的id是主键,也可以理解为唯一的索引字段
EXPLAIN SELECT * FROM t1 WHERE id IN (SELECT id FROM t2 WHERE t1.content = t2.content) OR content = 'a';
- index_merge:在查询过程中需要
多个索引组合使用
,通常出现在有 or 的关键字的sql中。
EXPLAIN SELECT * FROM t_emp WHERE deptId = 1 OR id = 1;
- ref_or_null:当对普通二级索引进行等值匹配,且该索引列的值也可以是NULL值时。
EXPLAIN SELECT * FROM t_emp WHERE deptId = 1 OR deptId IS NULL;
- fulltext:全文索引。
一般通过搜索引擎实现,这里我们不展开。
4. possible_keys 和 keys ☆
-
possible_keys
表示执行查询时可能用到的索引,一个或多个。 查询涉及到的字段上若存在索引,则该索引将被列出,但不一定被查询实际使用。 -
keys
表示实际使用的索引。如果为NULL,则没有使用索引。
EXPLAIN SELECT id FROM t1 WHERE id = 1;
5. key_len ☆
表示索引使用的字节数,根据这个值可以判断索引的使用情况,检查是否充分利用了索引,针对联合索引值越大越好。
如何计算:
-
先看索引上字段的类型+长度。比如:int=4 ; varchar(20) =20 ; char(20) =20
-
如果是varchar或者char这种字符串字段,视字符集要乘不同的值,比如utf8要乘 3,如果是utf8mb4要乘4,GBK要乘2
-
varchar这种动态字符串要加2个字节
-
允许为空的字段要加1个字节
-- 创建索引
CREATE INDEX idx_age_name ON t_emp(age, `name`);
-- 测试1
EXPLAIN SELECT * FROM t_emp WHERE age = 30 AND `name` = 'ab%';
-- 测试2
EXPLAIN SELECT * FROM t_emp WHERE age = 30;
6. rows ☆
MySQL认为它执行查询时必须检查的行数。值越小越好。
-- 如果是全表扫描,rows的值就是表中数据的估计行数
EXPLAIN SELECT * FROM t_emp WHERE empno = '10001';
-- 如果是使用索引查询,rows的值就是预计扫描索引记录行数
EXPLAIN SELECT * FROM t_emp WHERE deptId = 1;
7. Extra ☆
包含不适合在其他列中显示但十分重要的额外信息。通过这些额外信息来理解MySQL到底将如何执行当前的查询语句
。MySQL提供的额外信息有好几十个,这里只挑介绍比较重要的介绍。
7.1 Impossible WHERE:where子句的值总是false
EXPLAIN SELECT * FROM t_emp WHERE 1 != 1;
7.2 Using where:使用了where,但在where上有字段没有创建索引
EXPLAIN SELECT * FROM t_emp WHERE `name` = '风清扬';
7.3 Using temporary:使了用临时表保存中间结果
常见于groupby和orderby,效率较低
EXPLAIN SELECT DISTINCT content FROM t1;
7.4 Using filesort:排序无法使用索引,效率较低
在对查询结果中的记录进行排序时,是可以使用索引的,如下所示:
EXPLAIN SELECT * FROM t1 ORDER BY id;
如果排序操作无法使用到索引,只能在内存中(记录较少时)或者磁盘中(记录较多时)进行排序(filesort),如下所示:
EXPLAIN SELECT * FROM t1 ORDER BY content;
7.5 Using index:使用了覆盖索引
表示直接访问索引就足够获取到所需要的数据,不需要通过索引回表
EXPLAIN SELECT id, content1 FROM t4;
EXPLAIN SELECT id FROM t1;
7.6 Using index condition:索引下推优化
叫作 Index Condition Pushdown Optimization (索引下推优化)
如果没有索引下推(ICP)
,那么MySQL在存储引擎层找到满足content1 > 'z'
条件的第一条二级索引记录。主键值进行回表
,返回完整的记录给server层,server层再判断其他的搜索条件是否成立。如果成立则保留该记录,否则跳过该记录,然后向存储引擎层要下一条记录。如果使用了索引下推(ICP
),那么MySQL在存储引擎层找到满足content1 > 'z'
条件的第一条二级索引记录。不着急执行回表
,而是在这条记录上先判断一下所有关于idx_content1
索引中包含的条件是否成立,也就是content1 > 'z' AND content1 LIKE '%a'
是否成立。如果这些条件不成立,则直接跳过该二级索引记录,去找下一条二级索引记录;如果这些条件成立,则执行回表操作,返回完整的记录给server层。
-- content1列上有索引idx_content1
EXPLAIN SELECT * FROM t4 WHERE content1 > 'z' AND content1 LIKE '%a';
注意:如果这里的查询条件只有content1 > 'z'
,那么找到满足条件的索引后也会进行一次索引下推的操作,判断content1 > 'z’是否成立(这是源码中为了编程方便做的冗余判断)
7.7 Using join buffer:
在连接查询时,当被驱动表不能有效的利用索引时,MySQL会为其分配一块名为连接缓冲区(join buffer)的内存来加快查询速度
EXPLAIN SELECT * FROM t1, t2 WHERE t1.content = t2.content;
下面这个例子就是被驱动表使用了索引:
EXPLAIN SELECT * FROM t_emp, t_dept WHERE t_dept.id = t_emp.deptId;
8. 其他字段
8.1 table:显示这一行的数据是关于哪张表的
8.1.1 单表:
EXPLAIN SELECT * FROM t1;
8.1.2 多表关联:t1为驱动表,t2为被驱动表。
注意:
内连接时,MySQL性能优化器会自动判断哪个表是驱动表,哪个表示被驱动表,和书写的顺序无关
EXPLAIN SELECT * FROM t1 INNER JOIN t2;
8.2 partitions
代表分区表中的命中情况,非分区表,该项为NULL
8.3 ref
显示与key中的索引进行比较的列或常量。
-- ref=atguigudb.t1.id 关联查询时出现,t2表和t1表的哪一列进行关联
EXPLAIN SELECT * FROM t1, t2 WHERE t1.id = t2.id;
-- ref=const 与索引列进行等值比较的东西是啥,const表示一个常数
EXPLAIN SELECT * FROM t_emp WHERE age = 30;
8.4 filtered
最后查询出来的数据占所有服务器端检查行数(rows)的百分比
。值越大越好。
-- 先根据二级索引deptId找到数据的主键,有3条记录满足条件,
-- 再根据主键进行回表,最终找到3条记录,有100%的记录满足条件
EXPLAIN SELECT * FROM t_emp WHERE deptId = 1;
-- 这个例子如果name列是索引列则 filtered = 100 否则filtered = 10(全表扫描)
EXPLAIN SELECT * FROM t_emp WHERE `name` = '风清扬';