1、问题描述

项目用的是Mybatis Plus框架操作数据库,在使用batchSave批量插入方法的时候发现效率极低,插入2w数据花了6分钟,太恐怖了。

看了源码发现,项目的批量插入方法调用的是Mybatis Plus的BatchExcutor,用这个本意是将多次更新sql语句集合为一条更新语句,复用同一个sql连接更新数据。但是打印sql语句发现,实际上仍然是一条一条插入的,只不过是复用同一个prepareStatement而已。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-eYdXHEWB-1643113459225)(resource/image-20220124202735365.png)]

2、问题解决

方案一:之所以没有按照预期的拼接sql为一条是因为我们需要在连接数据库的时候设置参数 rewriteBatchedStatements

url: jdbc:mysql://${datasource.host}/${datasource.name}??rewriteBatchedStatements=true

方案二:配置解决的方式太麻烦,在微服务情况下又要改很多地方,所以我采用了第二种方案也是mybatis plus 提供的sql注入器

  • 继承框架提供的DefaultSqlInjector,实现自己的sql注入器
@Override
    public List<AbstractMethod> getMethodList(Class<?> mapperClass) {
        List<AbstractMethod> methodList = super.getMethodList(mapperClass);
        methodList.add(new InsertBatchSomeColumn());  // 加入批量插入方法
        return methodList;
    }
  • InsertBatchSomeColumn 也是框架提供的批量插入方法,一句话来讲就是将我们的多条插入sql拼接成一条sql,这里就不插代码了,当然你也可以自己写一个方法继承AbstractMethod
  • 继承BaseMaper,加上sql注入的方法名接口即可使用
public interface InjectionMapper<T> extends BaseMapper<T> {

    Integer insertBatchSomeColumn(Collection<T> entityList);

    int batchSize = 1000;  // 应为mysql对于太长的sql语句是有限制的,所以我这里设置每1000条批量插入拼接sql

    default Integer batchInsert(Collection<T> entityList) {

        int result = 0;
        Collection<T> tempEntityList = new ArrayList<>();
        int i = 0;
        for (T entity : entityList) {
            tempEntityList.add(entity);
            if (i > 0 && (i % batchSize == 0)) {
                result += insertBatchSomeColumn(tempEntityList);
                tempEntityList.clear();
            }
            i++;
        }

        if (ValidateUtil.isNotEmpty(tempEntityList)) {
            result += insertBatchSomeColumn(tempEntityList);
        }
        return result;
    }

}

3、总结

其实两种方法底层都是将sql语句拼接起来,只不过一个是我们手动拼接,一个是mysql帮我们拼接

其实还有一种方式就是在xml中写动态sql,foreach标签拼接,但是太麻烦了不是。而且也是拼接sql的方式

我看网上对于这种方法遇到过一个问题就是主键id自增出错的问题,但是我的id是自己设置的所以没有问题,在这里提个醒

上图是我修改后,插入效率的提升,可以说是指数倍提升吧,有用的话点个赞哦。