EXPLAIN可以帮助开发人员分析SQL问题,EXPLAIN显示了MySQL如何使用使用SQL执行计划,可以帮助开发人员写出更优化的查询语句。使用方法,在select语句前加上EXPLAIN就可以知道:
表的读取顺序
数据读取操作的操作类型
哪些索引可以使用
哪些索引被实际使用
表之间的引用
每张表有多少行被优化器查询
下面是一个最普通的查询语句,用EXPLAIN进行分析演示。
EXPLAIN select * from TableName;
会得到以下结果:
除了以SELECT开头的查询语句,其余的DELETE、INSERT、REPLACE以及UPOATE语句前边都可以加上EXPLAIN
id :SELECT识别符。这是SELECT查询序列号。在一个大的查询语句中每个SELECT关键字都对应一个唯一的id。
①查询中包含子查询的情况
EXPLAIN
SELECT * FROM s1 WHERE id IN (SELECT id FROM s2) OR order_no = 'a';
这里虽然我们的查询语句是一个子查询,但是执行计划中s1和s2表对应的记录的id值全部是1,这就表明了查询优化器对查询语句进行重写,将子查询转换为了连接查询。
②查询中包含UNION语句的情况
SELECT * FROM s1
UNION SELECT * FROM s2 ;
这个语句的执行计划的第三条记录为什么这样?UNION子句会把多个查询的结果集合并起来并对结果集中的记录进行去重,怎么去重呢? MySQL使用的是内部的临时表。正如上边的查询计划中所示,UNION 子句是为了把id为1的查询和id为2的查询的结果集合并起来并去重,所以在内部创建了一个名为
③查询中包含UNION all的情况
EXPLAIN
SELECT * FROM s1 UNION ALL SELECT * FROM s2;
跟UNION 对比起来,UNION ALL就不需要为最终的结果集进行去重,它只是单纯的把多个查询的结果集中的记录合并成一个并返回给用户,所以也就不需要使用临时表。所以在包含UNION ALL子句的查询的执行计划中,就没有那个id为NULL的记录
table :每条记录都对应着某个单表,该条记录的table列,代表着该表的表名。
select_type: 表示SELECT语句的类型。
simple:简单select(不使用union或子查询)。
primary:对于包含union union all 或者子查询的大查询来说,最左边查询的select type 值为primary
union:对于包含union或者union all的大查询来说,除了最左边的那个查询以外,其余小查询的select type值为union
dependent union:在包含union 或者 union all的大查询中,如果各个小查询都依赖外层查询的话,除了最左边的那个小查询外,其余的select type为dependent union
union result:mysql使用临时表来完成union的去重工作,针对该临时表(union的结果)的查询的select type为union result
subquery:包含子查询的查询语句不能够转为对应的半连接形式,并且该查询不是相关子查询,查询优化器决定采用该子查询物化的方案来执行该子查询时,该子查询的第一个select关键字对应的select_type为subquery
dependent subquery:包含子查询的查询语句不能够转为对应的半连接形式,并且该查询是相关子查询,该子查询的第一个select关键字对应的select_type为dependent subquery
derived:采用物化的方式执行包含派生表的查询,该派生表对应的自查询的select_type就是derived
type:显示连接使用了何种类型。从最好到最差的连接类型为:system > const > eq_ref > ref > ref_or_null >range > index > ALL。一般来说,得保证查询至少达到range级别,最好能达到ref。
system:表仅有一行,这是const类型的特列,平时不会出现,这个也可以忽略不计。
const:当我们根据主键或者唯一二级索引列与常数进行等值匹配时,对单表的访问方法就是const。因为只匹配一行数据,所以很快。
例如将主键置于where列表中
-- 通过主键和常数值进行比较
EXPLAIN
select * from single_table where id = 400;
-- 通过唯一二级索引和常数值进行比较
EXPLAIN
select * from single_table where key2 = 100;
注意:如果主键或者唯一二级索引的索引列由多个列组成,则只有在索引列中的每一项都与常数进行等值比较时,这个const访问方法才有效(因为只有这样才能保证最多只有一条记录符合条件)
eq_ref:在连接查询时,如果被驱动表是通过主键或者唯一二级索引列等值匹配的方式进行访问的,则对该被驱动表的访问方法就是eq_ref。
(驱动表与被驱动表:A表和B表join连接查询,如果通过A表的结果集作为循环基础数据,然后一条一条地通过该结果集中的数据作为过滤条件到B表中查询数据,然后合并结果。那么我们称A表为驱动表(作为外层循环),B表为被驱动表(里层循环,需要不断的拿外层循环传进来的每条记录去匹配))
左连接中,左表是驱动表
右连接中,右表是驱动表
内连接中,mysql会自动选择数据量较小的表作为驱动表。
EXPLAIN
SELECT * FROM s1 INNER JOIN s2 ON s1.id = s2.id;
从执行计划的结果中可以看出,MySQL打算将s2作为驱动表,s1作为被驱动表,重点关注s1的访问方法是eq_ref,表明在访问s1表的时候可以通过主键的等值匹配来进行访问。
ref:普通的二级索引列与常数等值比较,那么对该表的访问方法就可能是ref。
对于普通的二级索引来说,通过索引列进行等值比较后可能匹配到多条连续的记录,而不是像主键或者唯一二级索引那样最多只能匹配1条记录,所以这种ref访问方法比const要差些,但是在二级索引等值比较时匹配的记录数较少时的效率还是很高的(如果匹配的二级索引记录太多那么回表的成本就太大了)。
EXPLAIN
SELECT * FROM s1 WHERE order_no = 'a';
ref_or_null:同时找出某个二级索引列的值等于某个常数值的记录,并且把该列中值为null的记录也找出来
select * from single_table where key1 = 'abc' or key1 is null
range:如果索引列并不全是等值查询的时候,还进行范围匹配的访问,为range利用索引,一般就是在你的where语句中出现了between、<、>、in等的查询。
这种范围扫描索引扫描比全表扫描要好,因为它只需要开始于索引的某一点,而结束于另一点,不用扫描全部索引。
select * from single_table where key_part1 = 'a' AND key_part2 > 'b';
EXPLAIN
SELECT * FROM s1 WHERE order_no IN ('a', 'b', 'c');
EXPLAIN
SELECT * FROM s1 WHERE order_no > 'a' AND order_no < 'b';
index:当我们可以使用索引覆盖,但需要扫描全部的索引记录时,该表的访问方法就是index。
EXPLAIN
SELECT insert_time FROM s1 WHERE expire_time = '2021-03-22 18:36:47';
ALL:对于每个来自于先前的表的行组合,进行完整的表扫描(性能最差)。
EXPLAIN
SELECT * FROM s1;
possible_keys:指出MySQL能使用哪个索引在该表中找到行。如果是空的,没有相关的索引。这时要提高性能,可通过检验WHERE子句,看是否引用某些字段,或者检查字段不是适合索引。
key:实际使用到的索引。如果为NULL,则没有使用索引。如果为primary的话,表示使用了主键。
EXPLAIN SELECT order_note FROM s1 WHERE
insert_time = '2021-03-22 18:36:47';
上述执行计划的possible keys列的值表示该查询可能使用到u_idx_day_status,idx_insert_time两个索引,然后key列的值是u_idx_day_status,表示经过查询优化器计算使用不同索引的成本后,最后决定使用u_idx_day_status来执行查询比较划算。
MySQL在执行计划中输出key_len列主要是为了让我们区分某个使用联合索引的查询具体用了几个索引列(复合索引有最左前缀的特性,如果复合索引能全部使用上,则是复合索引字段的索引长度之和,这也可以用来判定复合索引是否部分使用,还是全部使用),而不是为了准确的说明针对某个具体存储引擎存储变长字段的实际长度占用的空间到底是占用1个字节还是2个字节。
rows 预估的需要读取的记录条数
EXPLAIN
SELECT * FROM s1 WHERE order_no > 'z';
EXPLAIN
SELECT * FROM s1 WHERE order_no > 'a';
我们看到执行计划的rows列的值是分别是1和10573,这意味着查询优化器在经过分析使用查询的成本之后,觉得满足order_no> 'z’这个条件的记录只有1条,觉得满足order_no> ’ a '这个条件的记录有10573条。
Extra :说明—些额外的信息 ,该列包含MySQL解决查询的详细信息。
Distinct:MySQL发现第1个匹配行后,停止为当前的行组合搜索更多的行。
Not exists:MySQL能够对查询进行LEFT JOIN优化,发现1个匹配LEFT JOIN标准的行后,不再为前面的的行组合在该表内检查更多的行。
range checked for each record (index map: #):MySQL没有发现好的可以使用的索引,但发现如果来自前面的表的列值已知,可能部分索引可以使用。
Using filesort:MySQL需要额外的一次传递,以找出如何按排序顺序检索行。
Using index:从只使用索引树中的信息而不需要进一步搜索读取实际的行来检索表中的列信息。
Using temporary:为了解决查询,MySQL需要创建一个临时表来容纳结果。
Using where:WHERE 子句用于限制哪一个行匹配下一个表或发送到客户。
Using sort_union(…), Using union(…), Using intersect(…):这些函数说明如何为index_merge联接类型合并索引扫描。
Using index for group-by:类似于访问表的Using index方式,Using index for group-by表示MySQL发现了一个索引,可以用来查 询GROUP BY或DISTINCT查询的所有列,而不要额外搜索硬盘访问实际的表。
SQL优化之EXPLAIN执行计划
Explain执行计划
MySQL实战:explain详解(上)
基本每个文章写的都不全,只好各复制增删修改了一些,但是其实还是不全,只能了解个大概了。有兴趣的还是买个书看看吧。
ps:凌晨四点写的,看着墙上的小强陷入了沉思。