深入理解MySQL之Explain详解

文章目录

  • 前言
  • 示例表结构
  • 返回列总览
    • id
    • select_type
      • SIMPLE
      • PRIMARY
      • UNION
      • UNION RESULT
      • SUBQUERY
      • DEPENDENT SUBQUERY
      • DEPENDENT UNION
      • DERIVED
      • MATERIALIZED
      • UNCACHEABLE SUBQUERY
      • UNCACHEABLE UNION
    • table
    • type【重要】
      • system
      • const
      • eq_ref
      • ref
      • ref_or_null
      • index_merge
      • unique_subquery
      • index_subquery
      • range
      • index
      • ALL
    • possible_keys
    • key
    • key_len
    • ref
    • rows
    • Extra
      • No tables used
      • Impossible WHERE
      • No matching min/max row
      • Using index
      • Using index condition
      • Using where
      • Not exists
      • Using filesort
      • Using temporary
      • Select tables optimized away
      • Using join buffer (Block Nested Loop)

前言

一条查询语句在经过MySQL查询优化器的各种基于成本和规则的优化会后生成一个执行计划,这个执行计划展示了接下来具体执行查询的方式,比如多表连接的顺序是什么,对于每个表采用什么访问方法来具体执行查询等等。MySQL提供了EXPLAIN语句来帮助我们查看某个查询语句的具体执行计划,这篇博客的内容就是为了帮助大家理解EXPLAIN语句的各个输出项的含义,当我们要优化SQL时可以有针对性的提升某些查询语句的性能。

示例表结构

我MySQL的Docker镜像版本是5.6,不同的版本结果会有一点不一样,为了后面演示的需要,我这里建了两张表:

CREATE TABLE `s1` (
	`id` INT NOT NULL AUTO_INCREMENT,
	`key1` VARCHAR(100),
	`key2` VARCHAR(100),
	`key3` VARCHAR(100),
	`key_part1` VARCHAR(100),
	`key_part2` VARCHAR(100),
	`key_part3` VARCHAR(100),
	`common_field` VARCHAR(100),
	PRIMARY KEY (`id`),
	KEY idx_key1 (`key1`),
	UNIQUE KEY idx_key2 (`key2`),
	KEY idx_key3 (`key3`),
	KEY idx_key_part(`key_part1`, `key_part2`, `key_part3`)
) Engine=InnoDB CHARSET=utf8;
CREATE TABLE `s2` (
	`id` INT NOT NULL AUTO_INCREMENT,
	`key1` VARCHAR(100),
	`key2` VARCHAR(100),
	`key3` VARCHAR(100),
	`key_part1` VARCHAR(100),
	`key_part2` VARCHAR(100),
	`key_part3` VARCHAR(100),
	`common_field` VARCHAR(100),
	PRIMARY KEY (`id`),
	KEY idx_key1 (`key1`),
	UNIQUE KEY idx_key2 (`key2`),
	KEY idx_key3 (`key3`),
	KEY idx_key_part(`key_part1`, `key_part2`, `key_part3`)
) Engine=InnoDB CHARSET=utf8;

两张表除了表名外结构一样,除了id主键索引外,还为key1idx_key3建了普通索引,为key2建了唯一索引,为key_part1key_part2key_part3建了联合索引。s1表中有1000条数据,s2表中有10000条数据。

返回列总览

当我们想查看某个查询语句的执行计划的时候,可以在具体的查询语句前加一个EXPLAIN关键字:

EXPLAIN SELECT 1;

在这里插入图片描述
可以看到返回信息是这样的,各个字段的含义如下:

列名 描述
id 在一个大的查询语句中每个SELECT关键字都对应 一个唯一的id
select_type SELECT关键字对应的那个查询的类型
table 表名
partitions 匹配的分区信息
type 针对单表的访问方法
possible_keys 可能用到的索引
key 实际上使用的索引
key_len 实际使用到的索引⻓度
ref 当使用索引列等值查询时,与索引列进行等值匹配的对象信息
rows 预估的需要读取的记录条数
filtered 某个表经过搜索条件过滤后剩余记录条数的百分比
Extra 一些额外的信息

id

id列的编号是 select 的序列号,有几个 select 就有几个id,并且id的顺序是按 select 出现的顺序增长的。
id列值越大执行优先级越高,id值相同则从上往下执行,id值为 NULL 则最后执行。

EXPLAIN SELECT (SELECT key1 FROM s2 WHERE key2 = "a");

在这里插入图片描述
可以看到查询语句中有两个 SELECT 关键字,对应就有两个id值,且会先执行id值为2的s2表的子查询。

但是这里大家需要特别注意,查询优化器可能对涉及子查询的查询语句进行重写,从而转换为连接查询。所以如果我们想知道查询优化器对某个包含子查询的语句是否进行了重写,直接查看执行计划就好了,比如说:

EXPLAIN SELECT * FROM s1 WHERE key1 IN (SELECT key1 FROM s2);

在这里插入图片描述
可以看到,虽然我们的查询语句有一个子查询,但是执行计划中s1和s2表对应的记录的id值全部是1,这就表明了查询优化器将子查询转换为了连接查询。

对于包含UNION子句的查询语句来说,每个 SELECT 关键字对应一个id值也是没错的,不过既然提到了,肯定还是有点不一样的,比方说下边这个查询:

EXPLAIN SELECT * FROM s1 UNION SELECT * FROM s2;

在这里插入图片描述
UNION会把多个查询的结果集合并起来并对结果集中的记录进行去重,怎么去重呢?MySQL使用的是内部的临时表。正如上边的查询计划中所示,UNION子句是为了把id为1的查询和id为2的查询的结果集合并起来并去重,所以在内部创建了一个名为的临时表 (就是执行计划第三条记录的table列的名称),id为NULL表明这个临时表是为了合并两个查询的结果集而创建的。

再说一个连接查询的例子:

EXPLAIN SELECT * FROM s1 INNER JOIN s2;

在这里插入图片描述
可以看到,上述连接查询中参与连接的s1和s2表分别对应一条记录,但是这两条记录对应的id值都是1。这里需要注意的是,在连接查询的执行计划中,每个表都会对应一条记录,这些记录的id列的值是相同的,出现在前边的表表示驱动表,出现在后边的表表示被驱动表。所以从上边的EXPLAIN输出中我们可以看出,查询优化器准备让s1表作为驱动表,让s2表作为被驱动表来执行查询。

select_type

MySQL为每一个 SELECT 关键字代表的小查询都定义了一个称之为 select_type 的属性,只要知道了某个小查询的 select_type 属性,就知道了这个小查询在整个大查询中扮演了一个什么样的⻆色。取值如下:

名称 描述
SIMPLE Simple SELECT (not using UNION or subqueries) 【简单的SELECT(不使用UNION或子查询)】
PRIMARY Outermost SELECT 【最外层的SELECT】
UNION Second or later SELECT statement in a UNION 【UNION中的第二个或更后面的SELECT语句】
UNION RESULT Result of a UNION 【一个UNION的结果】
SUBQUERY First SELECT in subquery 【子查询中的第一个SELECT】
DEPENDENT SUBQUERY First SELECT insubquery, dependent on outer query【第一个SELECT子查询,取决于外部查询】
DEPENDENT UNION Second or later SELECT statement in a UNION, dependent on outer query 【UNION中的第二个或更后面的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 (see UNCACHEABLE SUBQUERY) 【UNION中属于不可缓存子查询的第二个或更高版本的选择(请参阅UNCACHEABLE子查询)】

SIMPLE

查询语句中不包含UNION或者子查询的查询都算作是 SIMPLE 类型,单表查询和连接查询都是 SIMPLE 类型的。

EXPLAIN SELECT * FROM s1;

在这里插入图片描述

EXPLAIN SELECT * FROM s1 INNER JOIN s2;

在这里插入图片描述

PRIMARY

对于包含UNION、UNION ALL或者子查询的大查询来说,它是由几个小查询组成的,其中最左边的那个查询的 select_type 值就是 PRIMARY,比方说:

EXPLAIN SELECT (SELECT key1 FROM s2 WHERE key2 = "a");

在这里插入图片描述

EXPLAIN SELECT * FROM s1 UNION SELECT * FROM s2;

在这里插入图片描述

UNION

对于包含UNION或者UNION ALL的大查询来说,它是由几个小查询组成的,其中除了最左边的那个小查询以外,其余的小查询的select_type值就是UNION,比如:

EXPLAIN SELECT * FROM s1 UNION SELECT * FROM s2;

在这里插入图片描述

UNION RESULT

MySQL选择使用临时表来完成UNION查询的去重工作,针对该临时表的查询的select_type就是UNION RESULT,比如:

EXPLAIN SELECT * FROM s1 UNION SELECT * FROM s2;

在这里插入图片描述

SUBQUERY

如果包含子查询的查询语句不能够转为对应的semi-join的形式,并且该子查询是不相关子查询,并且查询优化器决定采用将该子查询物化的方案来执行该子查询时,该子查询的第一个SELECT关键字代表的那个查询的 select_type 就是 SUBQUERY,通俗的说就是包含在SELECT中但是不在FROM子句中且和父查询没有搜索关联项的子查询,比如下边这个查询:

EXPLAIN SELECT (SELECT key1 FROM s2 WHERE key2 = "a");

在这里插入图片描述
可以看到,外层查询的 select_type 就是 PRIMARY,子查询的 select_type 就是 SUBQUERY。需要大家注意的是,由于 select_type 为 SUBQUERY 的子查询会被物化,所以只需要执行一遍。

DEPENDENT SUBQUERY

如果包含子查询的查询语句不能够转为对应的semi-join的形式,并且该子查询是相关子查询,则该子查询的第一个SELECT关键字代表的那个查询的 select_type 就是 DEPENDENT SUBQUERY,比如下边这个查询:

EXPLAIN SELECT * FROM s1 WHERE key1 IN (SELECT key1 FROM s2 WHERE key1 = 'a' UNION SELECT key1 FROM s1 WHERE key1 = 'b');

在这里插入图片描述
需要大家注意的是,select_type 为 DEPENDENT SUBQUERY 的查询可能会被执行多次。

DEPENDENT UNION

在包含UNION或者UNION ALL的大查询中,如果各个小查询都依赖于外层查询的话,那除了最左边的那个小查询之外,其余的小查询的 select_type 的值就是 DEPENDENT UNION,比方说下边这个查询:

EXPLAIN SELECT * FROM s1 WHERE key1 IN (SELECT key1 FROM s2 WHERE key1 = 'a' UNION SELECT key1 FROM s1 WHERE key1 = 'b');

在这里插入图片描述

DERIVED

对于采用物化的方式执行的包含派生表的查询,该派生表对应的子查询的 select_type 就是 DERIVED,或者可以认为包含在 FROM 子句中的子查询的 select_type 就是 DERIVED,MySQL会将结果存放在一个临时表中,也称为派生表。比方说下边这个查询:

EXPLAIN SELECT a.* FROM (SELECT * FROM s2 WHERE key2 = "a") a;

在这里插入图片描述
id为1的记录代表外层查询,注意它的table列显示的是,表示该查询是针对将派生表物化之后的s2表进行查询的。

MATERIALIZED

当查询优化器在执行包含子查询的语句时,选择将子查询物化之后与外层查询进行连接查询时,该子查询对应的 select_type 属性就是 MATERIALIZED,比如下边这个查询:

UNCACHEABLE SUBQUERY

不常用

UNCACHEABLE UNION

不常用

table

不论我们的查询语句有多复杂,里边儿包含了多少个表,到最后也是需要对每个表进行单表访问的,所以MySQL规定EXPLAIN语句输出的每条记录都对应着某个单表的访问方法,该条记录的table列代表着该表的表名。

EXPLAIN SELECT * FROM s1 INNER JOIN s2;

在这里插入图片描述
可以看到这个连接查询的执行计划中有两条记录,这两条记录的table列分别是s1和s2,分别说明对s1表和s2表的访问方式。

type【重要】

这一列表示关联类型或访问类型,即MySQL决定如何查找表中的行,查找数据行记录的大概范围。
一般来说,这些访问方法按照介绍的顺序性能依次变差。常见的访问类型从最优到最差分别为:system > const > eq_ref > ref > range > index > ALL
其中除了ALL这个访问方法外,其余的访问方法都能用到索引,除了 index_merge访问方法外,其余的访问方法都最多只能用到一个索引。一般来说,得保证查询达到range级别,最好达到ref。

system

当表中只有一条记录并且该表使用的存储引擎的统计数据是精确的,那么对该表的访问方法就是 system,比如:

EXPLAIN SELECT * FROM (SELECT * FROM s2 WHERE id = 1) a;

在这里插入图片描述
由于子查询的返回值最多只有一条记录,所以外层查询的访问就是 system 级别的。

const

当根据主键或者唯一二级索引列与常数进行等值匹配时,对单表的访问方法就是 const,比如:

EXPLAIN SELECT * FROM s2 WHERE key2 = "a";

在这里插入图片描述

eq_ref

在连接查询时,如果被驱动表是通过主键或者唯一二级索引列等值匹配的方式进行访问的(如果该主键或者唯一二级索引是联合索引的话,所有的索引列都必须进行等值比较),则对该被驱动表的访问方法就是 eq_ref,比方说:

EXPLAIN SELECT * FROM s1 WHERE key2 IN (SELECT id FROM s2 where s1.key1 = s2.key1);

在这里插入图片描述
从执行计划的结果中可以看出,MySQL打算将s1作为驱动表,s2作为被驱动表,重点关注s2的访问方法是 eq_ref,表明在访问s2表的时候可以通过主键的等值匹配来进行访问。

ref

当通过普通的二级索引列与常量进行等值匹配时来查询某个表,可能会找到多个符合条件的行,那么对该表的访问方法就可能是 ref,比如:

EXPLAIN SELECT * FROM s1 WHERE key1 = "a";

在这里插入图片描述

ref_or_null

当对普通二级索引进行等值匹配查询,该索引列的值也可以是NULL值时,那么对该表的访问方法就可能是 ref_or_null,比如说:

EXPLAIN SELECT * FROM s1 WHERE key1 = 'a' OR key1 IS NULL;

在这里插入图片描述

index_merge

一般情况下对于某个表的查询只能使用到一个索引,但单表访问在某些场景下可以使用Intersection、Union、Sort-Union这三种索引合并的方式来执行查询,此时MySQL打算使用索引合并的方式来执行查询,比如:

EXPLAIN SELECT * FROM s1 WHERE key1 = 'a' OR key3 = 'a'; 

在这里插入图片描述

unique_subquery

类似于两表连接中被驱动表的eq_ref访问方法,unique_subquery 是针对在一些包含IN子查询的查询语句中,如果查询优化器决定将IN子查询转换为EXISTS子查询,而且子查询可以使用到主键进行等值匹配的话,那么该子查询执行计划的type列的值就是 unique_subquery,比如下边的这个查询语句:

EXPLAIN SELECT * FROM s1 WHERE key2 IN (SELECT id FROM s2 where s1.key1 = s2.key1) OR key3 = 'a';

在这里插入图片描述

index_subquery

与 unique_subquery 类似,只不过访问子查询中的表时使用的是普通的索引,比如:

EXPLAIN SELECT * FROM s1 WHERE key1 IN (SELECT key1 FROM s2 where s1.key3 = s2.key3) OR key3 = 'a';

在这里插入图片描述

range

如果使用索引获取某些范围区间的记录,那么就可能使用到 range 访问方法,范围扫描通常出现在 in(),between,>,<,>=,<=等操作中。比如下边的这个查询:

EXPLAIN SELECT * FROM s1 WHERE key1 IN ('a', 'b', 'c');

在这里插入图片描述

index

当我们可以使用索引覆盖,但需要扫描全部的索引记录时,该表的访问方法就是index。比如:

EXPLAIN SELECT key_part2 FROM s1 WHERE key_part3 = 'a';

在这里插入图片描述
上述查询中的搜索列表中只有key_part2一个列,而且搜索条件中也只有key_part3一个列,这两个列又恰好包含在idx_key_part索引中,可是搜索条件key_part3不能直接使用该索引进行ref或者range方式的访问,只能扫描整个idx_key_part索引的记录,所以查询计划的type列的值就是index。

对于使用InnoDB存储引擎的表来说,二级索引的记录只包含索引列和主键列的值,而聚簇索引中包含用户定义的全部列以及一些隐藏列,所以扫描二级索引的代价比直接全表扫描,也就是扫描聚簇索引的代价更低一些。

ALL

即全表扫描,意味着MySQL需要从头到尾去查找所需要的行。通常情况下这需要增加索引来进行优化了:

EXPLAIN SELECT * FROM s1;

在这里插入图片描述

possible_keys

表示在某个查询语句中,对某个表执行单表查询时可能用到的索引有哪些。可能出现 possible_keys 有列,而 key 显示 NULL 的情况,这种情况是因为表中数据不多,MySQL认为索引对此查询帮助不大,选择了全表查询。 如果该列是 NULL,则没有相关的索引。在这种情况下,可以通过检查 WHERE 子句看是否可以创造一个适当的索引来提高查询性能,然后用 Explain 查看效果。

key

这一列显示MySQL实际采用哪个索引来优化对该表的访问。如果没有使用索引,则该列是NULL。如果想强制MySQL使用或忽视 possible_keys 列中的索引,可以在查询中使用 force index 和 ignore index。

key_len

表示当优化器决定使用某个索引执行查询时,该索引记录的最大⻓度,通过这个值可以算出具体使用了索引中的哪些列。
它是由这三个部分构成的:

  • 对于使用固定⻓度类型的索引列来说,它实际占用的存储空间的最大⻓度就是该固定值,比如CHAR(10),那么该列实际占用的最大存储空间就是10个字节;对于指定字符集的变⻓类型的索引列来说,比如某个索引列的类型是VARCHAR(100),使用的字符集是utf8,那么该列实际占用的最大存储空间就是100 × 3 = 300个字节。
  • 如果该索引列可以存储NULL值,则key_len比不可以存储 NULL值时多1个字节。
  • 对于变⻓字段来说,都会有2个字节的空间来存储该变⻓列的实际⻓度。

一般的数值类型 tinyint 占1字节,smallint 占2字节,int 占4字节,bigint 占8字节
一般的时间类型 date 占3字节,timestamp 占4字节,datetime 占8字节

举例来说:

EXPLAIN SELECT * FROM s1 WHERE id > 5;

在这里插入图片描述
由于id列的类型是INT,并且不可以存储NULL值,所以在使用该列的索引时key_len大小就是4。

再比如:

EXPLAIN SELECT * FROM s1 WHERE key2 = "a";

在这里插入图片描述
由于key1列的类型是VARCHAR(100),所以该列实际最多占用的存储空间就是300字节,又因为该列允许存储NULL值,所以key_len需要加1,又因为该列是可变⻓度列,所以key_len需要加2,所以最后ken_len的值就是303。

EXPLAIN SELECT * FROM s1 WHERE key_part1 = "bb" AND key_part2 = "aa";

在这里插入图片描述
从key_len为606可以知道这个查询语句用到了联合索引的前两列,如果为909就说明联合索引的三列都用上了。

ref

当使用索引列等值匹配的条件去执行查询时,也就是在访问方法是const、eq_ref、ref、ref_or_null、unique_subquery、index_ subquery其中之一时,ref列展示的就是与索引列作等值匹配所用到的列或常量,比如:

EXPLAIN SELECT * FROM s1 WHERE key1 = 't';

在这里插入图片描述
可以看到ref列的值是const,表明在使用idx_key1索引执行查询时,与key1列作等值匹配的对象是一个常数,有时候更复杂一 点:

EXPLAIN SELECT * FROM s1 INNER JOIN s2 ON s1.id = s2.id;

在这里插入图片描述
可以看到对被驱动表s2的访问方法是eq_ref,而对应的ref列的值是ambition.s1.id,这说明在对被驱动表进行访问时会用到PRIMARY索引,也就是聚簇索引与一个列进行等值匹配的条件。

有的时候与索引列进行等值匹配的对象是一个函数,比方说下边这个 查询:

EXPLAIN SELECT * FROM s1 INNER JOIN s2 ON s2.key1 = UPPER(s1.key1);

在这里插入图片描述
可以看到对s2表采用ref访问方法执行查询,然后在查询计划的ref列里输出的是func,说明与s2表的key1列进行等值匹配的对象是一个函数。

rows

如果查询优化器决定使用全表扫描的方式对某个表执行查询时,执行计划的rows列就代表预计需要扫描的行数,如果使用索引来执行查询时,执行计划的rows列就代表预计扫描的索引记录行数。比如下边这个查询:

EXPLAIN SELECT * FROM s1 WHERE key1 > 't';

在这里插入图片描述
我们看到执行计划的rows列的值是58,这意味着查询优化器在经过分析使用idx_key1进行查询的成本之后,觉得满足条件的记录只有58条。注意这个只是估计要读取并检测的行数,可能和结果集里的行数不一致。

Extra

顾名思义,Extra列是用来说明一些额外信息的,我们可以通过这些额外信息来更准确的理解MySQL到底将如何执行给定的查询语句。常见的有这几个:

No tables used

当查询语句的没有FROM子句时将会提示该额外信息,比如:

EXPLAIN SELECT 1;

在这里插入图片描述

Impossible WHERE

查询语句的WHERE子句永远为FALSE时将会提示该额外信息, 比方说:

EXPLAIN SELECT * FROM s1 WHERE 1 != 1;

在这里插入图片描述

No matching min/max row

当查询列表处有MIN或者MAX聚集函数,但是并没有符合WHERE子句中的搜索条件的记录时,将会提示该额外信息 , 比方说:

EXPLAIN SELECT MIN(key1) FROM s1 WHERE key1 = 'abcdefg';

在这里插入图片描述
表里没有符合key1为’abcdefg’的记录。

Using index

当我们的查询列表以及搜索条件中只包含属于某个索引的列, 也就是在可以使用索引覆盖的情况下,在Extra列将会提示该额外信息。比方说下边这个查询中只需要用到idx_key1而不需要回表操作:

EXPLAIN SELECT key1 FROM s1 WHERE key1 = 'a';

在这里插入图片描述

Using index condition

有些搜索条件中虽然出现了索引列,但查询的列不完全被索引覆盖无法使用到索引,比 如下边这个查询:

EXPLAIN SELECT * FROM s1 WHERE key1 > 't' AND key1 LIKE '%fsf';

在这里插入图片描述
查询语句不是都能够使用到key1索引,且查询的列不完全被索引覆盖。
在以前版本的MySQL中,是按照下边步骤来执行这个查询的:

  • 先根据第一个条件,从二级索引idx_key1中获取到对应的二级索引记录。
  • 根据上一步骤得到的二级索引记录中的主键值进行回表, 找到完整的用户记录再检测该记录是否符合第二个条件,将符合条件的记录加入到最后的结果集。

但是虽然第二个条件不能组成范围区间参与range访问方法的执行,但这个条件毕竟只涉及到了key1列,MySQL优化了一下:

  • 先根据第一个条件,从二级索引idx_key1中获取到对应的二级索引记录。
  • 对于指定的二级索引记录,先不着急回表,而是先检测一 下该记录是否满足第二个条件,如果这 个条件不满足,则该二级索引记录压根儿就没必要回表。
  • 对于满足第二个条件的二级索引记录执行回表操作。

回表操作其实是一个随机IO,比较耗时,所以上述修改虽然只改进了一点点,但是可以省去好多回表操作的成本。MySQL把这个改进称之为索引条件下推(Index Condition Pushdown)
如果在查询语句的执行过程中将要使用索引条件下推这个特性,在Extra列中将会显示Using index condition。

Using where

当查询的列不能被索引覆盖,并且该语句的WHERE子句中有针对该表的搜索条件时,在Extra列中会提示上述额外信息。比如下边这个查询:

EXPLAIN SELECT * FROM s1 WHERE key1 > "a";

在这里插入图片描述

Not exists

当我们使用左(外)连接时,如果WHERE子句中包含要求被驱动表的某个列等于NULL值的搜索条件,而且那个列又是不允许存储NULL值的,那么在该表的执行计划的Extra列就会提示该额外信息,比如这样:

EXPLAIN SELECT * FROM s1 LEFT JOIN s2 ON s1.key1 = s2.key1 WHERE s2.id IS NULL;

在这里插入图片描述

Using filesort

很多情况下排序操作无法使用到索引,只能在内存中(记录较少的时候)或者磁盘中(记录较多的时候)进行排序,MySQL把这种在内存中或者磁盘上进行排序的方式统称为文件排序。如果某个查询需要使用文件排序的方式执行查询,就会在执行计划的Extra列中显示该额外信息,比如这样:

EXPLAIN SELECT * FROM s1 ORDER BY common_field LIMIT 10;

在这里插入图片描述
需要注意的是,如果查询中需要使用filesort的方式进行排序的记录非常多,那么这个过程是很耗费性能的,我们最好想办法将使用文件排序的执行方式改为使用索引进行排序。

MySQL中有两种文件排序方式:

  • 单路排序:一次性取出满足条件行的所有字段,然后在sort buffer中进行排序。
  • 双路排序(回表排序模式):首先根据相应的条件取出相应的排序字段和可以直接定位行数据的行ID,然后在 sort buffer 中进行排序,排序完后需要再次取回其它需要的字段。

MySQL 通过比较系统变量 max_length_for_sort_data(默认1024字节) 的大小和需要查询的字段总大小来判断使用哪种排序模式:

  • 如果 max_length_for_sort_data 比查询字段的总长度大,那么使用单路排序模式
  • 如果 max_length_for_sort_data 比查询字段的总长度小,那么使用双路排序模式

比如这个查询语句:

EXPLAIN SELECT * FROM s1 WHERE key_part1 = "aa" ORDER BY key_part3;

在这里插入图片描述
先看单路排序的详细过程:

  • 从索引 idx_key_part 找到第一个满足 key_part1 = ‘aa’ 条件的主键 id
  • 根据主键 id 取出整行,取出所有字段的值,存入 sort_buffer 中
  • 从索引 idx_key_par t找到下一个满足 key_part1 = ‘aa’ 条件的主键 id
  • 重复直到不满足 key_part1 = ‘aa’
  • 对 sort_buffer 中的数据按照字段 key_part3 进行排序
  • 返回结果给客户端

再看下双路排序的详细过程:

  • 从索引 idx_key_part 找到第一个满足 key_part1 = ‘aa’ 条件的主键 id
  • 根据主键 id 取出整行,把排序字段 key_part3 和主键 id 这两个字段放到 sort buffer 中
  • 从索引 idx_key_part 取下一个满足 key_part1 = ‘aa’ 记录的主键 id
  • 重复直到不满足 key_part1 = ‘aa’
  • 对 sort_buffer 中的字段 key_part3 和主键 id 按照字段 key_part3 进行排序
  • 遍历排序好的 id 和字段 key_part3,按照 id 的值回到原表中取出所有字段的值返回给客户端

对比两个排序模式,单路排序会把所有需要查询的字段都放到 sort buffer 中,而双路排序只会把主键和需要排序的字段放到 sort buffer 中进行排序,然后再通过主键回到原表查询需要的字段。
如果MySQL排序内存配置的比较小并且没有条件继续增加了,可以适当把 max_length_for_sort_data 配置小点,让优化器选择使用双路排序算法,可以在sort_buffer 中一次排序更多的行,只是需要再根据主键回到原表取数据。
如果MySQL排序内存有条件可以配置比较大,可以适当增大 max_length_for_sort_data 的值,让优化器优先选择全字段排序(单路排序),把需要的字段放到 sort_buffer 中,这样排序后就会直接从内存里返回查询结果了。
所以,MySQL通过 max_length_for_sort_data 这个参数来控制排序,在不同场景使用不同的排序模式, 从而提升排序效率。

Using temporary

在许多查询的执行过程中,MySQL可能会借助临时表来完成一 些功能,比如去重、排序之类的,比如我们在执行许多包 含DISTINCT、GROUP BY、UNION等子句的查询过程中,如果不能有效利用索引来完成查询,MySQL很有可能寻求通过建立内部的临时表来执行查询。如果查询中使用到了内部的临时表,在执行计划的Extra列将会显示该额外信息,比方说这样:

EXPLAIN SELECT DISTINCT common_field FROM s1;

在这里插入图片描述
再比如:

EXPLAIN SELECT common_field, COUNT(*) AS amount FROM s1 GROUP BY common_field;

在这里插入图片描述
上述执行计划的Extra列不仅仅包 含Using temporary提示,还包含Using filesort提示, 可是我们的查询语句中明明没有写ORDER BY子句,这是因为MySQL会在包含GROUP BY子句的查询中默认添加上ORDER BY子句,也就是说上述查询其实和下边这个查询等价:

EXPLAIN SELECT common_field, COUNT(*) AS amount FROM s1 GROUP BY common_field ORDER BY common_field;

如果我们并不想为包含GROUP BY子句的查询进行排序,需要我们显式的写上ORDER BY NULL,就像这样:

EXPLAIN SELECT common_field, COUNT(*) AS amount FROM s1 GROUP BY common_field ORDER BY NULL;

在这里插入图片描述
执行计划中没有Using filesort的提示了,也就意味着执行查询时可以省去对记录进行文件排序的成本了。
另外,执行计划中出现Using temporary并不是一个好的征兆,因为建立与维护临时表要付出很大成本的,所以我们最好能使用索引来替代掉使用临时表,比方说下边这个包含GROUP BY子句的查询就不需要使用临时表:

EXPLAIN SELECT key1, COUNT(*) AS amount FROM s1 GROUP BY key1;

在这里插入图片描述

Select tables optimized away

当查询列表处有MIN或者MAX聚集函数来访问存在索引的某个字段时,会提示该额外信息,比如:

EXPLAIN SELECT MAX(key1) FROM s1;

在这里插入图片描述

Using join buffer (Block Nested Loop)

在连接查询执行过程过,当被驱动表不能有效的利用索引加快访问速度,MySQL一般会为其分配一块名叫join buffer的内存块来加快查询速度,也就是我们所讲的基于块的嵌套循环算法。比如下边这个查询语句:

EXPLAIN SELECT * FROM s1 INNER JOIN s2 ON s1.common_field = s2.common_field;

在这里插入图片描述

你可能感兴趣的:(MySQL)