前言
mybatis的动态sql一直广受好评,因为节约了大量手动sql的麻烦,尤其是其中的foreach标签,在执行批量操作时,简直如虎添翼。但是相信不少人在使用中发现,使用foreach做批量操作有时会无与伦比的慢,可能长达数十秒甚至分钟级别,我们今天就来验证一下该现象
一、准备工作
1. 环境准备
系统:windows 10
CPU:I7-6700K
数据库:mysql8.0
工程:springboot 2.5.2
版本:mybatis 3.5.6
JDBC驱动:8.0.25
2. 创建资源
建立一张 id + 15个 varchar 字段的表 usertest
对应的java对象及构造方法:
@Data
public class UserTest {
private Integer id;
private String name1;
private String name2;
private String name3;
private String name4;
private String name5;
private String name6;
private String name7;
private String name8;
private String name9;
private String name10;
private String name11;
private String name12;
private String name13;
private String name14;
private String name15;
public UserTest () {
}
public UserTest(Integer id, String name1) {
this.id = id;
this.name1 = name1;
this.name2 = name1 + "a";
this.name3 = name1 + "b";
this.name4 = name1 + "c";
this.name5 = name1 + "d";
this.name6 = name1 + "e";
this.name7 = name1 + "f";
this.name8 = name1 + "g";
this.name9 = name1 + "h";
this.name10 = name1 + "i";
this.name11 = name1 + "j";
this.name12 = name1 + "k";
this.name13 = name1 + "l";
this.name14 = name1 + "m";
this.name15 = name1 + "n";
}
}
使用 foreah 的动态sql则如下
<insert id="addUser" parameterType="com.zhanfu.springboot.demo.entity.UserTest" >
insert into usertest(id,name1,name2,name3,name4,name5,name6,name7
,name8,name9,name10,name11,name12,name13,name14,name15) values
<foreach collection="list" item="item" separator=",">
(
#{item.id}, #{item.name1},#{item.name2},#{item.name3},#{item.name4},#{item.name5},
#{item.name6},#{item.name7},#{item.name8},#{item.name9},#{item.name10},#{item.name11},
#{item.name12},#{item.name13},#{item.name14},#{item.name15}
)
</foreach>
</insert>
二、执行测试
先写可以一直运行的方法:
private int sequence = 1;
public boolean addUser(Integer count) {
List<UserTest> list = new ArrayList<>();
int max = sequence + count;
for (; sequence < max; sequence++) {
UserTest usertest = new UserTest(sequence , sequence + "zf");
list.add(usertest);
}
long start = System.currentTimeMillis();
boolean res = userMapper.addUser(list);
System.out.println("列表大小" +list.size()+ ",耗时:" + (System.currentTimeMillis() - start));
return res;
}
1. 基准测试
我们先看只插入一个对象时,使用的时间,可以看到在第一次插入时,耗时最长,因为此时与数据库连接等操作需要执行,可忽略第一次的数据。
我们以后面5次的耗时为准,可以看到,对象的插入平均耗时在 8ms 左右
2. 批量测试
- 100 条, 平均总耗时为 34ms,平均单条耗时0.34ms
- 500 条, 平均总耗时为 117ms,平均单条耗时0.23ms
- 2000 条, 平均总耗时为 340ms,平均单条耗时0.17ms
- 5000 条, 平均总耗时为 700ms,平均单条耗时0.14ms
- 10000 条, 平均总耗时为 1361ms,平均单条耗时0.14ms
后经查看数据库,上述数据均准确完成插入,所以测试是有效的,一次语句插入10000条以上一般比较少见,不再往上测试
三、加大载荷
从上述测验可以看出,没有出现预料中的性能问题,考虑是否是字段还不够多、或单字段太短导致的?因此再次做出调整,
首先将VARCHAR字段数量增加至25个,长度也不再控制,使用默认的255.
然后实际使用的字符串长度,保持和日常经验一致的15字符左右
加大载荷后,我们直接从2000开始测试,五次测试结果如下:
结果为:
2000 条, 平均总耗时为 500ms,平均单条耗时0.25ms
5000 条, 平均总耗时为 1187ms,平均单条耗时0.23ms
10000 条, 平均总耗时为 2465ms,平均单条耗时0.25ms
检查数据库也是正常插入完成
相比上次的测试结果,数据越多,显得越慢。
在10000条的时候,相比之前耗时增长了大约80%,但考虑到表格从16个字段增加至26个字段,本身就增加了60%多。所以这样的情况是完全在预期中的,距离实机那种插入几百行到千行,就会长达几十秒甚至分钟级别的耗时仍有着数量级差距。
四、测试结论
在测试中,即使到了一次批量插入10000条数据,仍然没有出现问题,平均单条耗时反而越来越短。但是在生产环境中,却屡次出现性能问题,甚至在千条数据规模时,能达到几十秒,其中数据库执行耗时在百毫秒的范围内,确定原因是拼接sql过程耗时太长。
无法复现,未出现性能问题,验证失败!
初步怀疑是 mybatis 与 jdbc 版本升级后,导致以前的性能问题得到解决,有待继续确认。