写了好多年了,求波点赞,收藏,关注,一键三连!!
个人博客:http://59.110.230.15欢迎捧场!
最近有个需求,一张主表4张子表,主表添加数据后,4张子表分表要批量新增多条记录。比如:Person insert一条记录;childOne,childTwo,childThree,childFour4张表分别添加10条记录。由此引发的批量操作的几种方式的性能测试。
搭建一个测试spring boot2.4.4项目
mybatis依赖
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.4.2</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.20</version>
</dependency>
配置集成druid数据源,初始和最大连接配置为50
模拟表如下:
主表
@Data
@TableName("person")
public class Person {
@QuerySqlField(index = true)
@TableId(type = IdType.INPUT)
private long id;
@QuerySqlField
@TableField
private String name;
}
子表:
@Data
@TableName("childone")
public class ChildOne {
@TableId
private int id;
@TableField
private int parentId;
@TableField
private String json;
}
字段完全一致,只是名字为ChildTwo。。。以此来推。使用json字段保存一个长json数据用来模拟稍大一些的数据。
然后分别进行5种方案测试,每种方案测3组(1主表+10*4子表;1主表+100*4子表;1主表+500*4子表;)取avg
方案1 一次发送多个表的insert语句,并且每个子表的insert语句采用单条insert+多values的模式
<insert id="insertAll" parameterType="com.wm.demo1.entity.PersonAll">
insert into person(id,name) values (#{person.id},#{person.name});
insert into childone(id,parent_id,json) values
<foreach collection="listOne" item="child" open="" separator="," close="">
(#{child.id},#{child.parentId},#{child.json})
</foreach>
;
insert into childtwo(id,parent_id,json) values
<foreach collection="listTwo" item="child" open="" separator="," close="">
(#{child.id},#{child.parentId},#{child.json})
</foreach>
;
insert into childThree(id,parent_id,json) values
<foreach collection="listThree" item="child" open="" separator="," close="">
(#{child.id},#{child.parentId},#{child.json})
</foreach>
;
insert into childFour(id,parent_id,json) values
<foreach collection="listFour" item="child" open="" separator="," close="">
(#{child.id},#{child.parentId},#{child.json})
</foreach>
;
</insert>
方案2 一次发送多个表的insert语句,每个字标的insert语句采用foreach整个insert拼接模式
<insert id="insertAll2" parameterType="com.wm.demo1.entity.PersonAll">
insert into person(id,name) values (#{person.id},#{person.name});
<foreach collection="listOne" item="child" open="" separator=";" close="">
insert into childone(id,parent_id,json) values
(#{child.id},#{child.parentId},#{child.json})
</foreach>
;
<foreach collection="listTwo" item="child" open="" separator=";" close="">
insert into childtwo(id,parent_id,json) values
(#{child.id},#{child.parentId},#{child.json})
</foreach>
;
<foreach collection="listThree" item="child" open="" separator=";" close="">
insert into childThree(id,parent_id,json) values
(#{child.id},#{child.parentId},#{child.json})
</foreach>
;
<foreach collection="listFour" item="child" open="" separator=";" close="">
insert into childFour(id,parent_id,json) values
(#{child.id},#{child.parentId},#{child.json})
</foreach>
;
</insert>
方案3 mybatis plus saveBatch模式
long start = System.currentTimeMillis();
transactionTemplate.execute(status -> {
long start1 = System.currentTimeMillis();
personMapper.insert(person);
log.info("testMultiInsert,主表:{}", System.currentTimeMillis() - start1);
long start2 = System.currentTimeMillis();
childOneService.saveBatch(listOne,listOne.size());
log.info("testMultiInsert,listOne:{}", System.currentTimeMillis() - start2);
long start3 = System.currentTimeMillis();
childTwoService.saveBatch(listTwo,listTwo.size());
log.info("testMultiInsert,listTwo:{}", System.currentTimeMillis() - start3);
long start4 = System.currentTimeMillis();
childThreeService.saveBatch(listThree,listThree.size());
log.info("testMultiInsert,listThree:{}", System.currentTimeMillis() - start4);
long start5 = System.currentTimeMillis();
childFourService.saveBatch(listFour,listFour.size());
log.info("testMultiInsert,listFour:{}", System.currentTimeMillis() - start5);
return true;
});
log.info("使用mybatis plus 的saveBatch方式,time:{}", System.currentTimeMillis() - start);
方案4 采用java代码foreach遍历执行mapper的insert方法
long start = System.currentTimeMillis();
transactionTemplate.execute(status -> {
long start1 = System.currentTimeMillis();
personMapper.insert(person);
log.info("testMultiInsert2,主表:{}", System.currentTimeMillis() - start1);
long start2 = System.currentTimeMillis();
for (ChildOne child : listOne) {
childOneMapper.insert(child);
}
log.info("testMultiInsert2,listOne:{}", System.currentTimeMillis() - start2);
long start3 = System.currentTimeMillis();
for (ChildTwo child : listTwo) {
childTwoMapper.insert(child);
}
log.info("testMultiInsert2,listTwo:{}", System.currentTimeMillis() - start3);
long start4 = System.currentTimeMillis();
for (ChildThree child : listThree) {
childThreeMapper.insert(child);
}
log.info("testMultiInsert2,listThree:{}", System.currentTimeMillis() - start4);
long start5 = System.currentTimeMillis();
for (ChildFour child : listFour) {
childFourMapper.insert(child);
}
log.info("testMultiInsert2,listFour:{}", System.currentTimeMillis() - start5);
方案5 采用一个表发送一次,sql采用单insert拼接多个多value模式
long start = System.currentTimeMillis();
transactionTemplate.execute(status -> {
long start1 = System.currentTimeMillis();
personMapper.insert(person);
log.info("testMultiInsert3,主表:{}", System.currentTimeMillis() - start1);
long start2 = System.currentTimeMillis();
childOneMapper.insertAll(listOne);
log.info("testMultiInsert3,listOne:{}", System.currentTimeMillis() - start2);
long start3 = System.currentTimeMillis();
childTwoMapper.insertAll(listTwo);
log.info("testMultiInsert3,listTwo:{}", System.currentTimeMillis() - start3);
long start4 = System.currentTimeMillis();
childThreeMapper.insertAll(listThree);
log.info("testMultiInsert3,listThree:{}", System.currentTimeMillis() - start4);
long start5 = System.currentTimeMillis();
childFourMapper.insertAll(listFour);
log.info("testMultiInsert3,listFour:{}", System.currentTimeMillis() - start5);
return true;
});
log.info("使用一个子表一次发送,每次发送采用insert 多value方式。time:{}", System.currentTimeMillis() - start);
直接上次测试结果
新增10条记录
一次发送多个表的insert语句,insert使用多value方式。time:130
一次发送多个表的insert语句,insert使用多value方式。time:159
一次发送多个表的insert语句,insert使用多value方式。time:125
avg138一次发送多个表的insert语句,insert使用多insert语句方式,time:117
一次发送多个表的insert语句,insert使用多insert语句方式,time:130
一次发送多个表的insert语句,insert使用多insert语句方式,time:122
avg123使用mybatis plus 的saveBatch方式,time:186
使用mybatis plus 的saveBatch方式,time:171
使用mybatis plus 的saveBatch方式,time:172
avg176使用一个子表一次发送,每次发送采用insert 多value方式。time:141
使用一个子表一次发送,每次发送采用insert 多value方式。time:133
使用一个子表一次发送,每次发送采用insert 多value方式。time:137
avg137使用foreach循环调用mybatis plus 的单条insert方式,time:163
使用foreach循环调用mybatis plus 的单条insert方式,time:156
使用foreach循环调用mybatis plus 的单条insert方式,time:162
avg160
新增100条记录
一次发送多个表的insert语句,insert使用多value方式。time:229
一次发送多个表的insert语句,insert使用多value方式。time:253
一次发送多个表的insert语句,insert使用多value方式。time:279
avg254一次发送多个表的insert语句,insert使用多insert语句方式,time:297
一次发送多个表的insert语句,insert使用多insert语句方式,time:268
一次发送多个表的insert语句,insert使用多insert语句方式,time:281
avg282使用mybatis plus 的saveBatch方式,time:312
使用mybatis plus 的saveBatch方式,time:286
使用mybatis plus 的saveBatch方式,time:281
avg293使用一个子表一次发送,每次发送采用insert 多value方式。time:235
使用一个子表一次发送,每次发送采用insert 多value方式。time:226
使用一个子表一次发送,每次发送采用insert 多value方式。time:245
avg235使用foreach循环调用mybatis plus 的单条insert方式,time:500
使用foreach循环调用mybatis plus 的单条insert方式,time:739
使用foreach循环调用mybatis plus 的单条insert方式,time:580
avg606
新增500条记录
一次发送多个表的insert语句,insert使用多value方式。time:554
一次发送多个表的insert语句,insert使用多value方式。time:518
一次发送多个表的insert语句,insert使用多value方式。time:543
avg538一次发送多个表的insert语句,insert使用多insert语句方式,time:726
一次发送多个表的insert语句,insert使用多insert语句方式,time:682
一次发送多个表的insert语句,insert使用多insert语句方式,time:659
avg 689使用mybatis plus 的saveBatch方式,time:724
使用mybatis plus 的saveBatch方式,time:534
使用mybatis plus 的saveBatch方式,time:614
avg624rewriteBatchedStatements=false,使用mybatis plus 的saveBatch方式,time:849
rewriteBatchedStatements=false,使用mybatis plus 的saveBatch方式,time:944
rewriteBatchedStatements=false,使用mybatis plus 的saveBatch方式,time:1074
avg955使用一个子表一次发送,每次发送采用insert 多value方式。time:604
使用一个子表一次发送,每次发送采用insert 多value方式。time:594
使用一个子表一次发送,每次发送采用insert 多value方式。time:577
avg592使用foreach循环调用mybatis plus 的单条insert方式,time:1388
使用foreach循环调用mybatis plus 的单条insert方式,time:1467
使用foreach循环调用mybatis plus 的单条insert方式,time:1473
avg1442
根据测试结果得出我的几个结论:
1. 使用代码foreach循环一条条添加,性能极其差。这个应该是毋庸置疑的。数据量大于10就不建议使用了。
2. 发现会有两次saveBatch的测试,主要是验证rewriteBatchedStatements是否起作用,事实证明,使用saveBatch还是应该开启rewriteBatchedStatements=true,不然JDBC底层会无视executeBatch方法。还是一条条insert发送给MySql去执行。不过网上查到资料显示,saveBatch在批次处理数量小于3时,并不会批量传入而是会选择单条单条新增。(来源于网上资料,未验证过)
3. 对比一次发送的两种不同sql拼接方式,使用insert的多value格式,会比多insert性能略好一点。数据量越大性能好的越多,原理是因为使用多value模式可以减少sql解析
4.对比”一个表发送一次,每次发送isnert的多value方式“和”一次性发送全部表的添加sql和多条insert的表的insert语句采用多value方式“,性能差不多。略微有些偏差的地方猜测是多出的几次连接消耗。但是一条Sql insert多个表,开发时会比较困难,出现在一个表的mapper中耦合了其他表的insert操作。而且Mysql存在一个接收数据量大小的限制,当一次发送超过4M,就会报错。我测试的时候发现的就是大于500就会出现这个错,所以对于数量级要求会更小。而一个表发一次,更符合一般的开发规范,而且易于开发,不混乱。
5.暂时没有证据证明,自己写sql的多value模式一定比开启了rewriteBatchedStatements的mybatis plus saveBatch方法性能好。但是saveBatch用的sql魔板是单条新增的模板,理论上发送过去是多条insert语句而不是多value的一条insert语句,我觉得性能会好一些。目前来看性能方案4最差,方案1最好,其他差不多。 所以如果性能要求没有这么极致,我认为可以使用saveBatch快速开发。
6.还有个要注意的是saveBatch不支持oracle,oracle中批量新增,需要自己写sql一次发送多个insert语句。不然会变成一条条发送,降低效率。
这里是完整测试代码demo,mybatis plus 5种批量操作性能测试