前言

最近两天,我在公司优化了一些慢查询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类,轻松实现这个功能。

最后修改:2026 年 06 月 06 日
如果觉得我的文章对你有用,请随意赞赏