我们所有的查询语句,MySQL 都会为其选择一个最合适的执行计划。这个执行计划就展示了接下来执行查询的具体方式。在日常工作中我们可以在 SQL 语句前面加上 EXPLAIN 关键字来查看具体的执行计划。
举个例子:
这种就是我们日常用到 EXPLAIN 看到的最直接结果,也是这个查询语句最终的执行计划。这个笔记就是用来解释这个执行计划中的各个列分别对应的是什么意思。不过在整理具体之前,先简明扼要的说一下每个字段的具体含义:
列名 | 描述 | 备注 |
---|---|---|
id | 在一个大的查询中,每个 SELECT 对应一个唯一的 id | id 小的先执行 |
select_type | SELECT 关键字对应的查询类型 | 连接查询和子查询的时候才有用 |
partitions | 匹配的分区信息 | |
type | 针对单表的访问方式 | 我们最常用的字段 |
possible_Keys | 可能用到的索引 | |
key | 实际使用的索引 | |
key_len | 实际使用的索引长度 | |
ref | 当使用索引列等值查询时,与索引列等值匹配的对象信息 | |
rows | 预估的需要读取的记录条数 | |
filtered | 针对预估的需要读取的记录,经过搜索条件过滤后剩余记录条数的百分比 | |
Extra | 一些额外的信息 | 重点 |
执行计划输出中各列详解
id 列
通常来说一个查询语句都有一个或多个 SELECT ,在执行计划中每一个 SELECT 都会被单独分配一个 id。而 id 小的会被优先执行。为方便理解,我们举几个简单的例子:
这种单表等值查询,显而易见的只有一个 SELECT 而且实际上也只访问了一张表,所以下面的 id 只有一个是 1。如果我们使用外连接去查询两张表,但是只有一个 SELECT 的情况呢?
从结果上我们可以反过来推测,因为 SQL 中仅包含一个 SELECT,所以只有一个 id,值为 1;但是因为这个外连接查询分别查询了 s1 表和 s2 表,所以执行计划中有两行。要注意,执行计划的每一行都是针对某一个表的"子查询"。
在实际开发过程中,如果我们使用子查询或者 UNION 子句就可能出现多个 SELECT,这里我分别举一个 UNION 和 子查询 的例子。
如果你在实验过程中,发现你的子查询并没有因为有多个 SELECT 而分配不同的 id,请不要怀疑自己的知识认知,这只是因为你的子查询被 MySQL 的查询优化器进行了重写,转换为了连接查询(半连接)。
说一个特殊的情况,相信如果大家仔细阅读了上面的内容的话,就会发现在我们举的例子中出现了一个怪咖。就是 union 子句查询的执行计划中出现了一个 id 为 null 的情况
这个其实是一个内部的临时表,MySQL 为了让 id 为 1 和 2 的数据进行去重,他使用的是内部临时表,MySQL 在内部创建了一个名为 <union1,2> 的临时表。id 为 null 是表示这个表是临时的。
select_type 列
在前面的例子中我们也说到了一个查询语句中可能包含若干个 SELECT,查询若干个表。每一个表都是一个小查询,而 select_type 就是来说明这个小查询的类型的。
这一部分的笔记,我前期已经整理过了。如果大家感兴趣可以直接点击下面的连接去查看,我这里就不再赘述了。
详细解释MySQL explain 中的 select_type 是什么
table 列
无论我们的查询语句有多复杂,其中包含多少个表,使用什么连接、子查询、UNION 子句等方式进行组合,到最后还是对每个表进行单表访问。
EXPLAIN 语句的输出的每条记录都对应着某个单表的访问方法,该记录的 table 列代表该表的表名。
type 列
坦白的说,这一列应该是我们在查看 MySQL 的执行计划的时候最常看也是最无脑看的一列了。因为我们在学习 《Java 开发规范》的时候,上面明确规定了 SQL 的级别,原文如下:
开发规范中的这个所谓的级别,本质上就是说的 EXPLAIN 执行计划中 type 列的级别。完整的访问方法包括 system、const、eq_ref、ref、fulltext、ref_or_null、index_merge、unique_subquery、index_subquery、range、index、ALL。为了方便大家阅读,我先单独整理一个表格来说明各个级别的具体含义,然后再分别具体说明级别信息。
类型 | 描述 | 备注 |
---|---|---|
system | 如果表里面只有一条数据,而且表使用的存储引擎(如 MyISAM)的统计信息是准确的。 | 条件太苛刻,几乎见不到 |
const | 使用主键或唯一二级索引与常数进行等值匹配时 | 效率高,常见 |
eq_ref | 连接查询是,如果被驱动表是通过主键或者不允许为 null 的唯一二级索引列进行等值匹配的方法访问的 | 常见 |
ref | 通过普通的二级索引与常数进行等值匹配时 | 常见 |
fulltext | 全文索引 | 几乎用不到 |
ref_or_null | 对二级索引进行等值匹配且该索引的值也可以为 null 的时候 | 常见 |
index_merge | 两个以上的索引合并 | 不常见 |
unique_subquery | 如果子查询可以转换为 EXISTS 子查询,而且转换之后可以使用主键或者不允许为 null 的唯一二级索引进行等值匹配 | |
index_subquery | 如果子查询可以转换为 EXISTS 子查询,而且转换之后可以使用普通二级索引进行等值匹配 | |
range | 使用索引进行范围查询 | 常见 |
index | 使用索引覆盖,扫描前部索引记录的时候 | 常见,但是不推荐 |
ALL | 全表扫描 | 常见,但是不推荐 |
为了方便理解,每个我们都举一个具体的例子,当然如果你的时间有限,可以不看下面的内容,只需要记住上面的这个表格就可以了:
-
system 这个基本上见不到的,他要求表对应的存储引擎的统计信息是准确的,而我们通常是使用 InnoDB 存储引擎,而这个存储引擎的统计信息是不准确的。so,forget it;
-
const 这个就非常常见了,当我们使用主键索引或者唯一二级索引搜索非空的等值查询的时候,就是走的这个类型
比如说 SELECT * FROM info_information_unit WHERE id = 1
-
rq_ref 单独拿出这个类型来,可能你会觉得有点眼生,实时上,我们常常在用。因为我们表结构设计上通常会使用一个表的主键和另外一个表关联起来,这个时候被驱动表的类型就是 rq_ref
比如说:SELECT * FROM website_column wco LEFT JOIN website_label wl ON wco.id = wl.id WHERE wl.id = 123;
-
ref 这个其实我们也非常非常的常见,如果我们是使用我们添加的普通二级索引进行查询的话,他的级别就是 ref,例子太多了,我敢保证你的单标查询的 SQL 起码百分之五十以上这样的。
比如说:SELECT * FROM info_information_unit WHERE code = 'xxx';
-
fulltext 全文索引,其实目前 MySQL 支持了全文索引,而且现在也支持了中文的分词器。但是,实际上绝大多数场景下,针对分词查询这种倒排索引的应用场景,我们通常会使用更加成熟的 Elasticsearch。这里不展开讲述了。
-
ref_or_null 在我的理解里面,这种其实是一种特殊的 ref,和 ref 的区别在于他接受该索引列的值可是null 的情况。
比如说:SELECT * FROM info_information_unit WHERE code = 'a' or code IS NULL;
-
index_merge 我必须得说,学习这个类型之前,在我的认知里面,我们的查询 SQL 中无论可以匹配上多少个索引,最终都会通过查询优化器计算出成本最小的一个索引,然后使用这个索引对数据进行查询。而这种是在某种常见下可以使用 Intersection、union、sort-union 这 3 种索引合并的方式来执行查询的。
比如说:SELECT * FROM s1 WHERE key1 = 'a' AND key3 = 'a';
-
unique_subquery 和 index_subquery 这两个只是最终是否能够使用主键或者不允许存储 null 的唯一二级索引进行匹配这一点上有差异,其他都是一样的。所以我们把他们放在一起说。
他们都是针对一些包含 IN 子查询的查询语句,如果查询优化器决定将 IN 子查询转换为 EXISTS 子查询的话就会出现上面这种。
这里特殊说明一下,很多小伙伴对什么情况下查询优化器才会把 IN 子查询转换 EXISTS 子查询表示好奇,我后面会梳理查询优化器对 IN 语句的特殊关照的相关材料,等我。
-
range 这个大家看起来应该也很眼熟吧,如果我们使用范围查询的时候,经常看到他。
-
index 这个偶尔会看到,但是也不是很常见,这种情况下其实相当于是对于二级索引这棵树进行了全表扫描,他会扫描全部的索引记录,比如说我们查询联合索引的非第一个字段的时候,而且不需要回表的时候
-
ALL 这个最熟悉的全表扫描了,老实讲,我希望你的查询 SQL 的执行计划中看不到他。
possible_keys 列 和 key 列
在EXPLAIN 输出的执行计划中,possible_keys 列表示在某个查询语句中,对某表执行单标查询时可能用到的索引有哪些,而 key 则是则是表示,最终用到的索引是什么。举个例子:
但是需要注意的是,并不是可以供选的索引越多越好,因为可以供选择的索引越多,查询优化器在计算查询成本的时候花费的时间就越长。
ref 列
当我们的查询方法的类别是 const、eq_ref、ref、ref_or_null、unique_subquery、index_subquery 中的其中一个时,ref 列展示的就是与索引列进行等值查询的东西是啥。
它有时是一个常数,有时是一个列,甚至可以是一个 function。
rows 列
如果查询优化器决定使用全表扫描的方式对某个表执行查询时,执行计划的rows
列就代表预计需要扫描的行数,如果使用索引来执行查询时,执行计划的rows
列就代表预计扫描的索引记录行数。
filtered 列
filtered 列表示针对预估的需要读取的记录,经过搜索条件过滤后剩余记录条数的百分比。这个感觉说起来不好理解,我们可以举两个例子:
在这个查询中,我们对于表中 10107 条数据,由于没有搜索条件,所以他的过滤下的数据为 100%。
而在这个查询中,由于我们增加了搜索过滤条件,经过过滤大概还能剩下 10107 x 10% = 1010 条数据。
Extra 列
Extra 是用来说明一些额外的信息的,其实根据上面的内容,我们大概知道了一个 SQL 的执行计划输出列都是什么,分别代表什么意思。但是通过这列中的说明信息,我们可以更准确的理解 MySQL 到底如何执行给定的查询语句。
Extra 列中可以给出的声明信息非常非常的多,我手头的材料中也说明了很多,但是我在这里还是做了一些精简,因为这里面实际上有很多我们日常工作中根本用不上。我们只要对下面的这些描述有印象就可以了,如果遇到陌生的,可以在面向谷歌编程。
- No tables used: 查询语句中没有 FROM 子句
- Impossible WHERE 查询语句的 WHERE 子句条件永远为 false。 如: WHERE 1 != 1
- No matching min/max row :查询列表处有 MIN 或者 MAX 聚集函数,但是没有记录符合 WHERE 子句中的搜索条件
- Using index : 使用了覆盖索引。
- Using index condition : 搜索条件中虽然出现了索引类,但是却不能充当边界条件来形成扫描区间。比如 key1 > 'z' and key1 like '%a'
- Using where: 当某个搜索条件需要在 server 层进行判断时,提示 Using where
- Using join buffer:连接查询的执行过程中,当被驱动表不能有效的利用索引加快访问速度的时候,使用 join buffer 缓冲区来加快查询速度的时候,会提示这个。
- Using filesort:在有些情况下对结果集中的记录进行排序的时候,是可以使用到索引的。
- Using temporary: 在许多查询的执行过程中,借助临时表进行去重、排序等。