前言
最近两天,我在公司优化了一些慢查询SQL语句,积累了一些真实的案例,从这篇开始,后面会出多篇慢SQL相关的文章分享给大家,希望对你会有所帮助。
1. 案发现场
我从archery平台的慢SQL日志中查到了一条,非常长的SQL语句,耗时要8s左右。
那条SQL大概是这样的:
select id,name
from category
where (
full_path like '%123456%'
or full_path like '%123455%'
or full_path like '%123454%'
or full_path like '%123453%'
or full_path like '%123452%'
or full_path like '%123451%'
...
)
and level=4 and status=1这条SQL的意思是,根据父分类的id,查询所有下面的四级分类信息。
full_path字段是一个varchar类型,保存的是所有父分类的id,用逗号分割拼接而成的字符串,比如:111111,222222,123455。
这条SQL支持传入一个id集合,可以查询出任意级分类及其所有子分类下的所有四级分类。
对于mybatis中的代码是这样的:
<select id="getFourCategoryList" resultMap="resultMap">
select id,name
from category
<where>
<foreach collection="parentIdList" item="id" open="(" separator="or" close=")">
full_path like '%#{id}%'
</foreach>
and level=4 and status=1
</where>
</select>这样传入parentIdList集合,就可以生成开头的SQL语句了。
上面的SQL功能上没有问题,但执行性能上有点问题,需要做优化。
2. 第一次优化
出现慢SQL的问题,关键的原因是一次性传入的parentIdList集合的数据太多了。
由此,需要限制单次查询传入的parentIdList集合的大小。
List<List<String>> partitionList = Lists.partition(parentIdList,100);
List<Category> allDataList = Lists.newArrayList();
for(List<String> list: partitionList) {
List<Category> categoryList = categoryMapper.getFourCategoryList(list);
if(CollectionUtils.isNotEmpty(categoryList)) {
allDataList.addAll(categoryList);
}
return allDataList;
}这样已改造之后,如果父分类id集合传入100条数据,执行SQL之后,每次大概1s左右返回数据。
把数据再减少一点,每次传入50条数据?
这样确实可以降低单次的SQL执行速度,但是整个查询功能,耗时可能比之前要多一些,因为循环次数增加了,多了一些远程操作。
有没有其他的优化办法?
3. 第二次优化
其实,这里使用了大量的like关键字查询数据,性能是非常差的。
不用like,又如何查询字符串中的一部分数据呢?
答:可以使用find_in_set函数。
在我之前写的《MySQL如何将一行数据转换到多行?》那篇文章中介绍过这个函数的用法,它还是比较强大的。
将sql改成这样的:
select id,name
from category
where (
find_in_set('123456',full_path) !=0
or find_in_set('123455,full_path) !=0
or find_in_set('123454,full_path) !=0
or find_in_set('123453,full_path) !=0
or find_in_set('123452,full_path) !=0
or find_in_set('123451,full_path) !=0
...
)
and level=4 and status=1改造之后,执行sql,0.5s就查出数据了。
使用find_in_set函数比使用like关键字,查询数据的性能提升了一倍。
后续
这条慢SQL经过上面两次优化之后,性能基本可以满足要求了。
其实,还有可以进一步优化的空间。
如果想性能更快,可以Java代码的for循环中,使用多线程调用categoryMapper.getFourCategoryList(list);方法查询数据,然后将查询的结果进行汇总。
我们可以使用Java8中的CompletableFuture类,轻松实现这个功能。