前言
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 () { } 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 版本升级后,导致以前的性能问题得到解决,有待继续确认。