【mysql性能优化】- 执行计划explain

文章目录

  • 1 前言
  • 2 准备
  • 3 执行计划结果列的含义
    • 3.1 id -- 用来判断语句执行的顺序
    • 3.2 select_type -- 判断查询的类型
    • 3.3 table -- 当前行操作的数据是关于哪张表的
    • 3.4 type -- 索引类型
    • 3.5 possible_keys -- 可能用到的索引,是一种预测,不准
    • 3.6 key -- 实际使用到的索引
    • 3.7 key_len -- 索引的长度
    • 3.8 ref -- 索引中的哪一列被使用了
    • 3.9 rows -- 被索引优化查询的数据个数
    • 3.10 Extra -- 附加信息

官网:https://dev.mysql.com/doc/refman/8.0/en/explain-output.html

1 前言

本文用mysql5.5版本测试的
EXPLAIN + SQL 的输出结果为一个表,表头如下
在这里插入图片描述
该条sql语句在mysql里的具体执行信息(即执行计划)就藏在该表里。要想读明白这些信息,必须搞明白这个表里各列的具体含义。

2 准备

首先新建几张表和向表中插入几条数据为测试做准备

create table course
(
cid int(3),
cname varchar(20),
tid int(3)
);

create table teacher
(
tid int(3),
tname varchar(20),
tcid int(3)
);

create table teacherCard
(
tcid int(3),
tcdesc varchar(200)
);

为每个表插入几条数据

insert into course values(1, 'java', 1);
insert into course values(2, 'html', 1);
insert into course values(3, 'sql', 2);
insert into course values(4, 'web', 3);

insert into teacher values(1, 'tz', 1);
insert into teacher values(2, 'tw', 2);
insert into teacher values(3, 'tl', 3);

insert into teacherCard values(1, 'tzdesc');
insert into teacherCard values(2, 'twdesc');
insert into teacherCard values(3, 'tldesc');

插入数据之后我们现在有个需求:查询课程编号为2或教师证编号为3的老师信息。
sql语句即查询结果:

mysql> select t.* from teacher t, course c, teacherCard tc where t.tid = c.tid and t.tcid = tc.tcid and (c.cid =2 or tc.tcid =3);
+------+-------+------+
| tid  | tname | tcid |
+------+-------+------+
|    1 | tz    |    1 |
|    3 | tl    |    3 |
+------+-------+------+

下面我们看一下这条sql的执行计划,然后分析解释执行计划结果表中的没一列的意思

mysql> explain select t.* from teacher t, course c, teacherCard tc where t.tid = c.tid and t.tcid = tc.tcid and (c.cid =2 or tc.tcid =3);
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+----------------------------------------------------+
| id | select_type | table | partitions | type | possible_keys | key  | key_len | ref  | rows | filtered | Extra                                              |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+----------------------------------------------------+
|  1 | SIMPLE      | t     | NULL       | ALL  | NULL          | NULL | NULL    | NULL |    3 |   100.00 | NULL                                               |
|  1 | SIMPLE      | tc    | NULL       | ALL  | NULL          | NULL | NULL    | NULL |    3 |    33.33 | Using where; Using join buffer (Block Nested Loop) |
|  1 | SIMPLE      | c     | NULL       | ALL  | NULL          | NULL | NULL    | NULL |    4 |    25.00 | Using where; Using join buffer (Block Nested Loop) |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+----------------------------------------------------+

3 执行计划结果列的含义

3.1 id – 用来判断语句执行的顺序

id值相同时,从上往下顺序执行。所以上面的执行计划表示teacher表最新执行 --> 然后是teacherCard表 --> 最后是course表。
下面我们查询这个三张表中分别有几条数据

mysql> select * from teacher;
+------+-------+------+
| tid  | tname | tcid |
+------+-------+------+
|    1 | tz    |    1 |
|    2 | tw    |    2 |
|    3 | tl    |    3 |
+------+-------+------+
3 rows in set (0.00 sec)

mysql> select * from course;
+------+-------+------+
| cid  | cname | tid  |
+------+-------+------+
|    1 | java  |    1 |
|    2 | html  |    1 |
|    3 | sql   |    2 |
|    4 | web   |    3 |
+------+-------+------+
4 rows in set (0.00 sec)

mysql> select * from teacherCard;
+------+--------+
| tcid | tcdesc |
+------+--------+
|    1 | tzdesc |
|    2 | twdesc |
|    3 | tldesc |
+------+--------+
3 rows in set (0.00 sec)

查询结果得出 teacher表中有3条数据,course表中有4条数据,teacherCard表中有3条数据。通过上面的执行计划我们得出这个三个表的执行顺序是teacher --> teacherCard --> course
然后下面我们给teacher表添加三条数据

mysql> insert into teacher values(4, 'ta', 4);
Query OK, 1 row affected (0.01 sec)

mysql> insert into teacher values(5, 'tb', 5);
Query OK, 1 row affected (0.00 sec)

mysql> insert into teacher values(6, 'tc', 6);
Query OK, 1 row affected (0.00 sec)

这时候teacher表就有6条数据了,teacherCard表有3条数据,course表有4条数据
我们现在再执行一下上面的sql的执行计划

在这里插入图片描述
为此,我们得出一个结论。表的执行顺序会根据表中的数据量的变化而变化,为什么会有这样的结论呢?其实就是因为笛卡尔积的原理。
什么是笛卡尔积:
【mysql性能优化】- 执行计划explain_第1张图片

假如现在有a,b,c 三张表,这三张表中分别有2,3,4条数据,那么通过笛卡尔积得出最终数据有234=24条。然后我们改变一下a,b,c表的数据分别为4,3,2条,通过笛卡尔积得出的最终结果也是432=24条。虽然说234=24和432=24的结果是一样的,但是234=24首先执行的23=6,而432=24首先执行的是43=12,因为首先查出的数据是要放在内存中去的,对内存来说肯定希望数量越小越好。所以我们得出结论就是数据小的表优先查询。

下面我们验证一下这个结论
目前为止teacher表中有6条数据了,teacherCard表有3条数据,course表有4条数据。并且执行顺序为teacherCard–>course–>teacher;下面我们将course表中删掉两条数据,course表的数据就最少了,我们看看执行顺序是不是为course–>teacherCard–>teacher
【mysql性能优化】- 执行计划explain_第2张图片
上面是id值相同得出的结论,如果id值不同:id值越大越优先查询。
下面我们看一下另外一个需求
查询教授sql课程的老师的描述(tcdesc)
我们先用子查询的方式写sql看看其执行计划
【mysql性能优化】- 执行计划explain_第3张图片
本节小结

1 多表查询执行计划当id都相同时:从上往下顺序执行,并且得出表中的数据大小会影响表的执行顺序的结论,表中的数据少的先执行。
2 多表查询执行计划当id都不相同时:id值越大越先执行
3 多表查询执行计划当id即存在相同的又存在不同时:同样遵循越大越先执行,相同的id从上往下顺序执行

3.2 select_type – 判断查询的类型

查询的类型,主要是用于区别普通查询、联合查询、子查询等的复杂查询
【mysql性能优化】- 执行计划explain_第4张图片
1)PRIMARY:包含子查询sql中的主查询(最外层)
2)SUBQUERY:包含子查询sql中的子查询(非最外层)
我们还是以这个子查询sql的执行计划为例

explain select tc.tcdesc from teacherCard tc where tc.tcid =(select t.tcid from teacher t where t.tid = (select c.tid from course c where cname = 'sql'))

【mysql性能优化】- 执行计划explain_第5张图片
3)SIMPLE:简单查询(不包含子查询、union)
【mysql性能优化】- 执行计划explain_第6张图片
4)DERIVED:衍生查询(使用了零时表)

  • 在from子查询中只有一张表
    【mysql性能优化】- 执行计划explain_第7张图片
  • 在from子查询中,如果有table1 union table2,则table1 就是DERIVED,table2就是UNION
    【mysql性能优化】- 执行计划explain_第8张图片

5)UNION: 看上例
6)UNION RESULT:告知开发人员哪些表存在UNION查询,看上例

3.3 table – 当前行操作的数据是关于哪张表的

  • 直接显示表名或者表的别名
  • 由 ID 为 M,N 进行union 产生的表
  • < derivedN > 由 ID 为 N 衍生出来的表
  • < subqueryN> : 请参考 https://dev.mysql.com/doc/refman/8.0/en/subquery-materialization.html

3.4 type – 索引类型

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,要对type进行优化的前提:要有索引。
其中:system,const只是理想情况,实际能达到ref,range

1)system(忽略):这种类型基本上是达不到的,只有一条数据的系统表或衍生表只有一条数据的主查询能达到,但是这样的情况非常少。
创建一个test01表,插入一条数据,设置一个主键

mysql> create table test01
    -> (
    ->      tid int(3),
    ->      tname varchar(20)
    -> );       
Query OK, 0 rows affected (0.06 sec)

mysql> insert into test01 values(1, 'a');
Query OK, 1 row affected (0.03 sec)

mysql> alter table test01 add constraint tid_pk primary key(tid);
Query OK, 1 row affected (0.17 sec)
Records: 1  Duplicates: 0  Warnings: 0

写一个衍生表只有一条数据的主查询sql,看看它的执行计划
【mysql性能优化】- 执行计划explain_第9张图片
2)const:仅仅能查到一条数据的sql,并且只针对Primary key 或unique索引有效
【mysql性能优化】- 执行计划explain_第10张图片
下面我们将test01表的Primary key删掉,然后添加一个普通索引,再执行上面查询sql的执行计划
【mysql性能优化】- 执行计划explain_第11张图片
3)eq_ref:唯一性索引,对于每个索引键的查询,返回匹配唯一行数据(有且只有1个,不能多,不能为0),常见于唯一索引和主键索引中
【mysql性能优化】- 执行计划explain_第12张图片
【mysql性能优化】- 执行计划explain_第13张图片
【mysql性能优化】- 执行计划explain_第14张图片

explain select t.tcid from teacher t, teacherCard tc where t.tcid = tc.tcid;
总结一下,对于这条sql,用到索引时t.tcid,即teacher表中的tcid字段,满足了eq_ref级别的需要对索引查询查询的条件。另外还要满足teacher表中的tcid字段的数据个数和连接查询的结果一致(数据量要一致,并且数据值也要一致)才能满足eq_ref。所以要满足eq_ref级别的type也是非常有困难的。

4)ref(一般能达到这个级别,这级别比较重要):非唯一性索引,对于每个索引键的查询,返回匹配的所有行(0个或多个)
准备数据

insert into teacher values(4, 'tz', 4);
insert into teacherCard values(4, 'tz222');

【mysql性能优化】- 执行计划explain_第15张图片
下面我们针对teacher表的tname写一个sql,并且让其查询结果匹配多行,看能不能满足type为ref级别
【mysql性能优化】- 执行计划explain_第16张图片
5) range:检索指定范围的行,就是说where后面是个范围查询(between,> ,<,>=,<=等等;特殊:in有时候会失效,从而转为无索引即tpye为all)
【mysql性能优化】- 执行计划explain_第17张图片
6)index:查询全部索引中的数据
【mysql性能优化】- 执行计划explain_第18张图片
7)all:查询全部表中的数据
【mysql性能优化】- 执行计划explain_第19张图片

对type做个小的总结
system/const:返回结果只有一条数据
eq_ref:结果有多条,但是每一条数据是唯一的
ref:结果多条,但是每一条数据可以是0或者多条

3.5 possible_keys – 可能用到的索引,是一种预测,不准

显示可能应用在这张表中的索引,一个或多个。
查询涉及到的字段上若存在索引,则该索引将被列出,但不一定被查询实际使用

3.6 key – 实际使用到的索引

实际使用的索引,如果为 NULL ,则没有使用索引。

3.7 key_len – 索引的长度

作用:通常判断复合索引是否被完成使用。

-- 创建test_k1表
create table test_k1
(
      name char(20) not null default ''
);

-- 添加普通索引
alter table test_k1 add index index_name(name);

-- 查看sql执行计划
explain select * from test_k1 where name = '';

【mysql性能优化】- 执行计划explain_第20张图片

-- 给test_k1添加一个可以为空的name1字段
alter table test_k1 add column name1 char(20);

-- 给name1字段添加普通索引
alter table test_k1 add index index_name1(name1);

-- 查看sql执行计划
explain select * from test_k1 where name1 = '';

【mysql性能优化】- 执行计划explain_第21张图片

从上面可以得出结论:如果索引字段可以为Null,则会使用1个字节用于标识

-- 删掉test_k1表name和name1上面的索引
drop index index_name on test_k1;
drop index index_name1 on test_k1;

-- 给test_k1添加一个复合索引
alter table test_k1 add index name_name1_index(name, name1);

-- 查看sql执行计划
explain select * from test_k1 where name = '';
explain select * from test_k1 where name1 = '';

【mysql性能优化】- 执行计划explain_第22张图片
上面表中name字段和name1字段都是char()规定类型的值,下面我们看一下varchar可变长度的字段

-- 在test_k1 表中添加一个可变长度类型的字段
alter table test_k1 add column name2 varchar(20);

-- 给name2添加索引
alter table test_k1 add index name2_index(name2)--查看sql执行计划
explain select * from test_k1 where name2 = '';

【mysql性能优化】- 执行计划explain_第23张图片

从上面可以得出结论:如果索引字段可以为可变类型,则mysql会使用2个字节用于标识

ps : 在mysql中针对不同的编码分配不同的字节
utf8:1个字符3个字节
gbk:1个字符2个字节
latin:1个字符1个字节

3.8 ref – 索引中的哪一列被使用了

显示索引的哪一列被使用了,如果可能的话,是一个常数。哪些列或常量被用于查找索引列上的值。
【mysql性能优化】- 执行计划explain_第24张图片

3.9 rows – 被索引优化查询的数据个数

官网介绍如下:

The rows column indicates the number of rows MySQL believes it must examine to execute the query.
For InnoDB tables, this number is an estimate, and may not always be exact.

3.10 Extra – 附加信息

官网上列出了很多可能的值,我这里总结一些工作中可能会经常用到的。

1)using filesort(尽量不要出现): 没有用索引进行的排序,需要“额外”的一次排序,性能消耗大。常见于order by语句中
【mysql性能优化】- 执行计划explain_第25张图片

针对单索引做个小结:如果排序和查找的是同一个字段,则不会出现using filesort;如果排序和查找的不是同一个字段,那么就会出现using filesort;

-- 删除test02 中的index_a1,index_a2, index_a3索引
drop index index_a1 on test02;
drop index index_a2 on test02;
drop index index_a2 on test02;

-- 在test02表上创建一个复合索引
alter table test02 add index index_a1_a2_a3(a1,a2,a3);

-- 查看sql执行计划
explain select * from test02 where a1 = '' order by a3;
explain select * from test02 where a2 = '' order by a3;
explain select * from test02 where a1 = '' order by a2;

【mysql性能优化】- 执行计划explain_第26张图片

针对复合索引:不能跨界(最佳左前缀),就不能出现Using filesort。

2)using temporary(尽量不要出现): 需要额外使用一张临时表,性能消耗比较大。一般出现在group by 语句中。
【mysql性能优化】- 执行计划explain_第27张图片
3)using index(比较好的sql):使用到了覆盖索引。性能比较好,因为只从索引文件中获取数据(不需要回表查询)
在mysql的官网里貌似没提覆盖索引这个概念,但是它有这样一段描述:
The column information is retrieved from the table using only information in the index tree without having to do an additional seek to read the actual row. This strategy can be used when the query uses only columns that are part of a single index.
相信如果看懂了上篇文章《【myql性能优化】- mysql索引及底层数据结构》的话,其实这句话是很好理解的。它的意思就是当你要查询的数据,直接可以通过索引树获得的话,那这种查询就是using index的。

这里应该至少可以从三个方面去理解:

  • 直接从聚簇索引里获取到想要的数据
  • 直接从非聚簇索引里获取到想要的数据
  • 先通过非聚簇索引获取到该索引对应的主键值,然后拿着主键值再去聚簇索引里找到想要的数据
    举个小栗子:
    【mysql性能优化】- 执行计划explain_第28张图片
  1. using where:需要回表查询

假设age是索引列
当查询语句select age,name from … where age = …,此语句必须回原表查询name值,因为索引表中没有这个值,这就是回表查询。extra中会出现using where

在这里插入图片描述

你可能感兴趣的:(mysql)