背景

616大促前夕压测检查出有几个供应链的系统有SQL慢查询,于是DBA来了一场大部分系统mysql慢查询全扫描,揪出我们组负责的几个系统用大于有100条慢SQL(查询速度>300ms),下面就是我这次100条慢SQL排查的总结。

慢SQL的原因

1.没有索引

这个没什么好说的,有些数据表数据量比较小,但是由于多张表join之后产生了数据膨胀,所以速度也会很慢。

2.Using filesort

Using filesort 表示的是一个排序操作不能通过索引来完成,需要额外进行外部的排序动作。所以其实跟文件没什么关系~
解决filesort最根本的方式还是让排序字段走索引,比如:

select * from t where a = 12 and b in(1,2) order by c;
# 可以创建 idx_a_b_c 的联合索引,确保c排序能够走索引

另外有filesort并不代表SQL一定会慢
group by 会默认有排序,所以即使SQL没有order by,有group by也可能出现filesort

3.Using temporary

Extra出现Using temporary意味着MySQL需要创建一个临时表来容纳结果。这次分析的过程遇到最多就是这种SQL产生临时表

select * from a 
	join b on a.id=b.user_id 
	join (select user_id,group_concat(dept_id) from c group by user_id) t1 on t1.user_id=a.id
	order by a.create_time

本次大部分解决方式都是将这种子查询拆分换成join,再做group by

4.mysql优化器选择了错误的驱动表

遇到很多这种SQL,

select * from a 
	join b on a.id=b.user_id 
	join c on a.id=c.user_id
	where a.age<100 
	order by a.create_time

explain的结果,发现驱动表变成b,有b表的记录比较少,mysql的优化器选择了b做为驱动表,导致没有按照我们预期的a作为驱动表进行关联,出现了fielsort和temporary。
这类解决的大部分方式就是 强行使用straight_join 指定驱动表,比如

select * from a 
	straight_join b on a.id=b.user_id 
	straight_join c on a.id=c.user_id
	where a.age<100 
	order by a.create_time

5.使用not in,会导致不命中索引

not in会导致索引失效

6.对索引字段做函数处理,导致索引失效

比如

 select * from t where t.field_name like '%_time'   
 and str_todate(t.field_value) > '2020-06-12'

即使t.field_value做了索引,也不会命中,可以改成参数类型改为跟字段一致的作比较,而不是直接转换字段内容,比如t.field_value>’2020-06-12’

7.SQL太多的代码逻辑

认识的一个架构师说到一句比较合理的话,SQL更关注的是存储而不是计算,把计算逻辑放到mysql远没有在应用层面操作成本低。

8.left join改为join、straight_join

从业务上看,如果可以没有必要用 left join 就用 join,straight_join,避免mysql的调整导致SQL很慢

9.子表没有跟驱动表直接关联

驱动表join了N张表,但是直接跟驱动表关联的只有一部分,比如:

select * from a join b on on a.id=b.user_id  
join c on b.user_id=c.user_id and c.role_id = 'admin'  
join d on b.user_id = d.adminId   
where a.create_time > '2020-06-20' group by a.name  

其实如果 b c 和d表是有直接关联关系的,可以考虑让 b c d三张表一起join得出一个子查询,再拿子查询与驱动表join,其实速度有可能会快很多很多。

10.using index

建索引的时候注意,如果可以使用到索引覆盖就直接使用索引覆盖。

11.隐式转换导致不能走索引

遇到比较的是,a.value = b.user_id , a的value是varchar,别的user_id是int,导致没有命中索引
这种问题其实可以通过 explain + show warnings 来分析出来

12.走正确索引了还是慢

遇到过一次,SQL写得非常正确规范,但是SQL还是花了1.X秒,explain的结果发现是,命中索引过滤出的行数还是有几十万,导致SQL查询慢。
解决这个问题还是从业务上去解决,添加业务上的过滤条件来过滤出更少的数据,比如加上 create_time > (now() - 7),把一些无用的老数据过滤掉

额外的发现

1.DBA建议不用前缀索引

前缀索引说白了就是对文本的前几个字符(具体是几个字符在建立索引时指定)建立索引,这样建立起来的索引更小,所以查询更快
前缀索引能有效减小索引文件的大小,提高索引的速度。但是前缀索引也有它的坏处:MySQL 不能在 ORDER BY 或 GROUP BY 中使用前缀索引,也不能把它们用作覆盖索引(Covering Index)。

2.关闭索引合并

index_merge 表示 对多个索引分别进行条件扫描,然后将它们各自的结果进行合并(intersect/union)。
如果是intersect类型,取多个索引的交集,但是这种DBA会见直接创建联合索引,因为考虑到一些情况,比如命中A索引的区分度特别高,另一个B索引的字段区分度特别低,那么A查完了还一直在等B,会有可能导致查询速度下降。 union类型 就是多个索引条件扫描,对得到的结果进行并集运算,显然是多个条件之间进行的是 OR 运算。