MyBatis-Plus的saveBatch批量插入为何效率很低耗时长详解及解决方案

1、分析

参考:https://huaweicloud.csdn.net/63355e78d3efff3090b54618.html

(1)MySQL数据库

针对MySQL数据库saveBatch批量插入效率比较低,是比较好解决的,一般都是由于数据库连接url上没有配置批量操作的属性,只需要在url上加上如下属性即可:

rewriteBatchedStatements=true

即类似如下:
jdbc:mysql://数据库地址/数据库名?useUnicode=true&characterEncoding=UTF8&allowMultiQueries=true&rewriteBatchedStatements=true

加上之后,你就会发现,saveBatch的速度直线提升,效果还是很不错的,一万条数据估计也就在几百毫秒。

(2)Oracle数据库

Oracle数据库的问题就比较大了,而且至今潘老师也没找到一个比较完美的解决方案,此次写这篇博客也正是由于Oracle数据库saveBatch效率贼低引起的,先看下图,批量插入一万条数据(MyBatis-Plus的saveBatch默认一次1000条,1w条会分10次,当然你也可以设置Batch Size),耗时竟然达到10s多,简直不能忍啊,堪比龟速!

image.png

于是就开始各种排查,经过一番仔细debug、翻源码,反复测试对比验证,最终发现是因为MyBatis-Plus的针对Oracle主键序列生成策略导致的,在上图打印的日志可以看出,在每次打印参数之前,都会先执行下SELECT XXX.NEXTVAL FROM DUAL,这是我们在实体Entity上加上了MyBatis-Plus的注解@KeySequence(value = “XXX”)引起的,本来预想的1万条只需要和数据库交互10次就解决了,现在看打印日志情况,预估是insert和查询序列合计预计2万次交互,于是,测试了下去掉@KeySequence注解,手工给id赋好值,再次批量保存1万条,结果如下:


image.png

好家伙,直接干到1s多点,整整节约了10倍的时间,病根终于确定了,就是@KeySequence注解导致的。

但你这就想把锅甩给MyBatis-Plus那就大错特错了,这锅归根到底其实还是MyBatis的,为什么呢?经过潘老师一番深入探查,发现正如MyBatis-Plus官方所说,只对MyBatis做扩展却不改变,做一对快乐的好基友,@KeySequence(value = “XXX”)作用就是结合KeyGenerator来实现自动化生成主键并回填,但是MyBatis-Plus的所有KeyGenerator也是从MyBatis继承扩展而来,而使用了之后,对于Oracle而言就相当于在insert语句前加上了selectKey语句,类似如下:

<selectKey keyColumn="id" keyProperty="id" resultType="int" order="BEFORE">
select XXX.nextval from dual
</selectKey>

这是不是很熟悉,就是我们学MyBatis时候学的啊,MyBatis-Plus只不过是把它变成了@KeySequence注解,省去了你写这段xml了而已,而所有问题的源头就来自于这段xml,潘老师亲自测试,在insert前加上这段xml后使用Mybatis原生的SqlSession sqlSession = sqlSessionTemplate.getSqlSessionFactory().openSession(ExecutorType.BATCH)批量插入,发现批量插入无效,耗时和MyBatis-plus差不多,1w条大概10多秒,但是一旦去掉这段xml,再使用原生的Batch插入,我的天,竟然只要500多毫秒,也就是0.5秒,这差距在20倍左右,一直以为是MyBatis-Plus的锅,原来是MyBatis的锅,这下舒服了,可以安心地用MyBatis-Plus了,除非你不用mybatis了~


image.png

2、修改
xml中sql都是由MyBatis-Plus动态帮我们生成的,包括id为insert的,经过测试发现,如果我们在xml中自己再新定义一个id为insert的sql语句(注意id必须为insert),就将原来的默认生成的覆盖掉了,直接使用我们自己定义的,那么问题就迎刃而解了,具体如下:
a)去掉KeyGenerator实体对象的配置
b)去掉@KeySequence注解
c)id-type: 设置为AUTO
d)在实体对应的xml新增id为insert的sql,类似如下:

<insert id="insert" parameterType="user">
insert into t_user(id, username, password)
values(SEQ_USER.NEXTVAL,#{username},#{password})
</insert>

e)继续调用MyBatis-Plus的saveBatch或save,都会走我们写的这个insert对应的xml
f)测试后1w条大概在几百毫秒。

3、优化
(1)主键生成策略
IdType.AUTO:表示主键自增,适用于数据库支持的自增主键,如 MySQL 的 AUTO_INCREMENT。
IdType.ASSIGN_ID:使用雪花算法(Snowflake Algorithm)生成主键。
IdType.ASSIGN_UUID:生成一个不包含中划线的 UUID 作为主键。
IdType.INPUT:表示主键值需要手动输入或设置。
参考SEQ_USER.NEXTVAL类似的思路,实际实现的时候,对IdType.ASSIGN_ID进行扩展,从数据库进行序列化获取值。在并发量较大的情况下,该序列的获取也是一个瓶颈。分析发现获取是每次获取一条,后续优化获取可以按照递增的数量来批量获取,并在实体组装的时候,通过单独调用批量获取序号组装参数。

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容