Explain 执行计划
什么是执行计划
有了慢查询语句后,就要对语句进行分析。一条查询语句在经过 MySQL 查询优化器的各种基于成本和规则的优化会后生成一个所谓的执行计划,这个执行计划展示了接下来具体执行查询的方式,比如多表连接的顺序是什么,对于每个表采用什么访问方法来具体执行查询等等。EXPLAIN 语句来帮助我们查看某个查询语句的具体执行计划,我们需要搞懂EPLATNEXPLAIN 的各个输出项都是干嘛使的,从而可以有针对性的提升我们查询语句的性能。
通过使用 EXPLAIN 关键字可以模拟优化器执行 SQL 查询语句,从而知道 MySQL 是如何处理你的 SQL 语句的。分析查询语句或是表结构的性能瓶颈,总的来说通过 EXPLAIN 我们可以
- 表的读取顺序
- 数据读取操作的操作类型
- 哪些索引可以使用
- 哪些索引被实际使用
- 表之间的引用
- 每张表有多少行被优化器查询
执行计划的语法
执行计划的语法其实非常简单:在 SQL 查询的前面加上 EXPLAIN 关键字就行。比如:
EXPLAIN select * from table1
重点的就是 EXPLAIN 后面你要分析的 SQL 语句除了以 SELECT 开头的查询语句,其余的 DELETE、INSERT、REPLACE 以及 UPDATE语句前边都可以加上 EXPLAIN,用来查看这些语句的执行计划,不过我们这里对 SELECT 语句更感兴趣,所以后边只会以 SELECT 语句为例来描述 EXPLAIN 语句的用法。
执行计划详解
为了让大家先有一个感性的认识,我们把 EXPLAIN 语句输出的各个列的作用先大致罗列一下:
explain select * from order_exp;
id: 在一个大的查询语句中每个 SELECT 关键字都对应一个唯一的 id
select_type: SELECT 关键字对应的那个查询的类型
table:表名
partitions:匹配的分区信息
type:针对单表的访问方法
possible_keys:可能用到的索引
key:实际上使用的索引
key_len:实际使用到的索引长度
ref:当使用索引列等值查询时,与索引列进行等值匹配的对象信息
rows:预估的需要读取的记录条数
filtered:某个表经过搜索条件过滤后剩余记录条数的百分比
Extra:—些额外的信息
看到这里,是不是一脸懵逼,这是正常的,这里把它们都列出来只是为了描述一个轮廓,随着我们课程的进行,我们会仔细讲解每个列的含义,显示值的含义。
为了方便学习,我们使用范例表 order_exp:
这个表在库中有个三个派生表 s1,s2,order_exp_cut,表结构基本一致,有少许差别:
注意:为了方便讲述,我们可能会适当调整对列的讲解顺序,不会完全按照 EXPLAIN语句输出列顺序来讲解。
table
不论我们的查询语句有多复杂,里边包含了多少个表,到最后也是需要对每个表进行单表访问的,MySQL 规定 EXPLAIN 语句输出的每条记录都对应着某个单表的访问方法,该条记录的 table 列代表着该表的表名。
可以看见,只涉及对 s1 表的单表查询,所以 EXPLAIN 输出中只有一条记录,其中的table 列的值是 s1,而连接查询的执行计划中有两条记录,这两条记录的 table 列分别是 s1和 s2.
id
我们知道我们写的查询语句一般都以 SELECT 关键字开头,比较简单的查询语句里只有
一个 SELECT 关键字,
稍微复杂一点的连接查询中也只有一个 SELECT 关键字,比如:
SELECT *FROM s1 INNER J0IN s2
ON s1.id = s2.id
WHERE s1.order_status = 0 ;
但是下边两种情况下在一条查询语句中会出现多个 SELECT 关键字:
1、查询中包含子查询的情况
比如下边这个查询语句中就包含 2 个 SELECT 关键字:
SELECT* FROM s1 WHERE id IN ( SELECT * FROM s2);
2、查询中包含 UNION 语句的情况
比如下边这个查询语句中也包含 2 个 SELECT 关键字:
SELECT * FROM s1 UNION SELECT * FROM s2 ;
查询语句中每出现一个 SELECT 关键字,MySQL 就会为它分配一个唯一的 id 值。这个id 值就是 EXPLAIN 语句的第一个列。
单 SELECT 关键字
比如下边这个查询中只有一个 SELECT 关键字,所以 EXPLAIN 的结果中也就只有一条id 列为 1 的记录∶
EXPLAIN SELECT * FROM s1 WHERE order_no = 'a';
连接查询
对于连接查询来说,一个 SELEOT 关键字后边的 FROM 子句中可以跟随多个表,所以在连接查询的执行计划中,每个表都会对应一条记录,但是这些记录的 id 值都是相同的,
比如:
EXPLAIN SELECT * FROM s1 INNER JOIN s2;
可以看到,上述连接查询中参与连接的 s1 和 s2 表分别对应一条记录,但是这两条记录对应的 id 值都是 1。这里需要大家记住的是,在连接查询的执行计划中,每个表都会对应一条记录,这些记录的 id 列的值是相同的。
包含子查询
对于包含子查询的查询语句来说,就可能涉及多个 SELECT 关键字,所以在包含子查询的查询语句的执行计划中,每个 SELECT 关键字都会对应一个唯一的 id 值,比如这样:
EXPLAIN SELECT * FROM s1 WHERE id IN (SELECT id FROM s2) OR order_no = 'a';
但是这里大家需要特别注意,查询优化器可能对涉及子查询的查询语句进行重写,从而转换为连接查询。所以如果我们想知道查询优化器对某个包含子查询的语句是否进行了重写,直接查看执行计划就好了,比如说:
EXPLAIN SELECT * FROM s1 WHERE id IN (SELECT id FROM s2 WHERE order_no = 'a');
可以看到,虽然我们的查询语句是一个子查询,但是执行计划中 s1 和 s2 表对应的记录的 id 值全部是 1,这就表明了查询优化器将子查询转换为了连接查询,
包含 UNION 子句
对于包含 UNION 子句的查询语句来说,每个 SELECT 关键字对应一个 id 值也是没错的,不过还是有点儿特别的东西,比方说下边这个查询:
EXPLAIN SELECT * FROM s1 UNION SELECT * FROM s2;