SQL 执行速度评估
假如现在有一张活动表,表结构如下:
1 | create table activities |
业务中需要查询目前正在生效中的活动有哪些,查询SQL 是下面这样的:
1 | select * from activities where start_time < now() and now() < end_time; |
以我们对索引的了解,活动表上已经建了 start_time 和 end_time 的联合索引 idx_start_end_time,上面的 SQL 肯定会通过 idx_start_end_time 索引去查询,因此上面的 SQL 执行速度应该不会慢的,但事实上真的是这样吗,如果不是这样的话,我们该如何去评估这个 SQL 的执行速度呢,那就是通过 EXPLAIN 指令,也是我们今天要重点介绍的内容,通过 explain 的执行结果我们可以看到以下内容:
1.SQL 语句中表的查询顺序
2.SQL 的查询类型
3.查询的是哪个分区表
4.走的是哪个索引
5.查询扫描行数
6.SQL 的执行方式
通过对这些内容进行分析之后我们就可以优化我们的 SQL 语句或者表的索引结构,进一步提高我们的查询效率
EXPLAIN 语法介绍
对于 explain 的使用,其实很简单,只要在要执行的 SQL 语句前面加上 explain 关键字就可以,以上面的 SQL 为例:
1 | explain select * from activities where start_time < now() and now() < end_time; |
执行结果如下:
从执行结果来看,有点类似于在查询一张包含上面输出结果中的这些字段的表一样,这些字段包括 id,select_type, table,partitions, type, possible_keys, key, key_len, ref, rows, filtered, Extra,这些字段当中有些我们稍微了解一下就可以,有些字段对于我们优化 SQL 有很大的帮助,需要重点关注
针对下面的示例,这里先准备一些表作为测试使用:
1 | create table cities |
EXPLAIN 各字段含义
上面我们提到通过 explain 的执行结果我们可以看到 SQL 执行过程中的一些关键信息,执行结果中每个字段都代表着不同的含义,在理解了这些字段的含义后,对我们优化 SQL 或索引都会起到很大的作用,我们来依次介绍这些字段的含义
id,table: id 和 table 两个字段可以放在一起来看,id 的值代表了 SQL 语句中表的读取顺序,值越大读取顺序越靠前,当 id 值相同时,table 列按照从上往下的读取顺序。
1 | explain select * from activities where city_code = (select code from cities limit 1); |
1 | explain select * from activities t1 join cities t2 on t1.city_code = t2.code; |
select_type: 表示 SQL 语句的查询类型,常见的值有:
1 | SIMPLE:简单查询,不包含 UNION 或者子查询 |
partitions: 如果查询的表是分区表,表示命中的数据记录所在的分区,对于不是分区表,值为 NULL
type: 表示查询所执行的类型,通过它可以看出查询是如何执行的,这一列可以说是我们要重点优化的对象,它的值比较重要,主要有以下类型:
1 | system > const > eq_ref > ref > fulltext > ref_or_null > index_merge > unique_subquery > index_subquery > range > index > ALL |
上面这些类型从左到右,查询性能依次降低,这里先不详细介绍每种类型的执行效果,先知道有这些类型即可,后面和 Extra 字段一起统一重点介绍。
possible_keys: 表示查询可能使用到的索引,也有可能里面的索引一个都没用到,取决于优化器分析之后的结果,也就是有可能该列上存在索引,但优化器分析之后决定不使用该索引
key: 表示查询过程中使用的索引,如果为 null 则表示该查询没有使用索引,该列的值有可能存在 possible_key 列中没有出现的索引,同时当查询满足覆盖索引的条件时,possible_keys 列为 NULL,索引仅在 key 列显示
key_len: 表示查询过程中使用的索引字节数
ref: 表示在查询索引时,哪些列或者常量被用来和索引的值进行比较
rows: 表示查询过程中预估需要扫描的行数,注意这是一个估算的值,并不是具体的值
filtered: 表示通过索引查询之后匹配到的数据量再经过 server 层 where 条件判断过滤之后剩余数据比例,比如说根据索引条件查询满足条件的数量是 100,经过其他条件过滤之后返回的结果数剩 60,filtered 的值就是 60%,通常这个值越大越好,直接经过索引条件查询得到的结果数就是最终查询的结果数肯定效率是更高的,但也不用过多的关注这个值,这个值对 SQL 性能的整体提升相比其他更重要的字段(type, Extra)来说并不是非常明显
Extra: 表示 SQL 语句执行过程中一些额外的关键信息,常见的值有:
1 | Using where,Using index,Using index condition,Using index for group-by,Using join buffer (Block Nested Loop),Using join buffer (hash join),Using join buffer (Batched Key Access),Using filesort,Using temporary |
Type 列解析
对于一个 SQL 的优化,我们重点应该关注的是 Type 和 Extra 这两个字段,或者说这两个字段展示出来的信息几乎关系着整个 SQL 执行效率,先来看 Type 字段常见的值的含义:
const: 主要是在通过主键或者唯一键等值查询的时候,type 字段的值是 const
1 | explain select * from cities where code = '330100'; |
eq_ref: 在联表查询查询时,A 表 join B 表,B 表是通过主键或者唯一键进行关联时,也就是在 A 表中只有唯一一条记录和 B 表中数据相匹配,那 B 表的 type 字段值就是 eq_ref 类型
1 | explain select * from activities t1 join cities t2 on t1.city_code = t2.code; |
ref: 通过普通索引进行等值查询,也就是表中可能有多条数据记录匹配,那 type 字段值就是 ref 类型
1 | explain select * from cities where name = '杭州'; |
ref_or_null: 通过普通索引进行等值查询,或者该字段为 null 的情况,注意该字段不能设置为 not null,那 type 字段值就是 ref_or_null 类型
1 | explain select * from cities where name = '杭州' or name is null; |
index_merge: 在 where 条件中的字段需要利用多个索引进行查询,这时 type 字段值就是 index_merge 类型,常见于多个索引字段进行 or 查询
1 | explain select * from cities where code = '330100' or name = '杭州'; |
range: 索引范围查询,这个可能见的相对比较多一点,常见于带 <>,>,>=,<,<=,between,in,like 等这些查询条件语句中
1 | explain select * from activities where start_time >= now(); |
index: 通过遍历索引树就能够得到相应的数据,相比下面的 All 类型来说要好一些,索引文件大小肯定要比数据文件大小要小,而且索引的根节点和第二层节点大概率处于内存当中,相对来说会更快一些,但注意这是索引树扫描,相比其他索引定位查询还是要慢很多的,所以不要看到使用上了索引就以为查询不慢
1 | explain select id, name from cities; |
All: 这个就是通常我们所说的没有可用的索引可以利用,直接全表扫描,相对来说性能是最差的,最直接的方法就是通过加索引来避免全表扫描
1 | explain select * from activities where name = '活动一'; |
Extra 列解析
上面已经介绍了 Type 字段常见的一些值的含义,但有时还需要配合 Extra 一起来看才能更好的进行优化,接下来我们一起来看 Extra 字段常见值含义:
no matching row in const table: 通常根据主键查询的数据在表中不存在,Extra 列的值是这个
Using index: 表示利用到了我们在之前文章里面提到的索引覆盖优化功能,通常是要查询的字段以及条件字段都已经在索引中存在,也就是说通过索引树就可以满足查询条件以及结果,不需要再到主键索引树中查询该记录完整的数据,减少回表的过程
Using index for group-by: 和 Using index 意思差不多,不过就是查询语句中如果包含了 distinct 和 group by 关键字的时候 Extra 里面的内容就是 Using index for group-by 了
Using index condition: 表示利用到了在我们在之前文章里面同样提到过的索引下推优化功能,在 “仅能利用最左前缀索引的场景” 下(无法能利用全部联合索引),对不在最左前缀索引中的其他联合索引字段加以利用,在遍历索引时,由只传入可以利用到的字段值,改成了多传入下推字段值进行过滤,过滤会减少遍历索引查出的主键条数,从而减少回表次数,提升整体性能,对于 Using index 和 Using index condition 不是很熟悉的话建议去看之前的文章 <<深入索引二>>
Using where: 表示该查询需要在 Server 层得到存储引擎返回的结果之后需要再次进行过滤,这种情况可能使用了索引,只不过存在其他条件不在索引字段中,也有可能就完全没有使用索引
Using join buffer (Block Nested Loop): 表示在 join 联表查询的时候,当被驱动表的联表字段上没有索引的时候,Extra 里面就会出现这个值,但注意的是在 MySQL 8.0 版本之后 Extra 里面的值是 Using join buffer (hash join),当出现不管是 Using join buffer (Block Nested Loop) 还是 Using join buffer (hash join) 我们都应该想办法进行优化,简单的做法就是在被驱动表的关联字段上建索引
Using join buffer (Batched Key Access): 表示在 join 联表查询的时,当被驱动表的联表字段上有索引,Extra 里面就会出现这个值,但注意的是需要将下面的优化开关设置如下
1 | set optimizer_switch='mrr=on,mrr_cost_based=off,batched_key_access=on'; |
Using temporary: 表示该查询需要借助临时表来存储中间查询结果才能进一步得到最终的查询结果,通常存在于 group by 的查询语句,同时又没有使用到索引的情况,这种 SQL 一般都需要优化
Using filesort: 表示该查询没办法利用现有的索引对结果进行排序,而需要借助第三方存储空间来进行排序才能得到最终的排序结果,通常存在于包含 order by 的查询语句中,同时又没有使用到索引的情况,这种 SQL 一般都需要优化
好了,上面主要介绍了 explain 执行结果中的一些字段含义,type 字段和 Extra 字段里面常见的一些值以及这些值的含义,通过熟悉这些我们可以大概知道 SQL 是怎么执行的,执行的速度怎么样,是否还有优化空间,下篇文章就来看两个常见的 SQL 优化案例。