MyBatis批量更新性能测试

本文使用JDK1.8,Tomcat7,dbcp连接池,MyBatis

众所周知,MyBatis中批量操作数据库是需要在sqlMap中写foreach的,像下面这样:

1
2
3
4
5
6
7
8
<update id="batchUpdate" parameterType="java.util.List">
update test_user
set remark=‘abc’
where
<foreach collection="list" separator="or" item="i" index="index" >
id=#{i.id}
</foreach>
</update>

在代码评审过程中,大家对foreach的性能提出了质疑,然后笔者进行了测试。

0.测试目标

测试三种场景:

  • 单线程,使用MyBatis的foreach,不开启事务拦截
  • 单线程,使用MyBatis的foreach,开启事务拦截
  • 多线程,使用java代码for循环提交到线程池

主要关注执行耗时

1.使用foreach,不开启事务拦截,批量插入1000条数据

1.1 代码

  • sqlMap

    1
    2
    3
    4
    5
    6
    <insert id="batchAdd" parameterType="java.util.List">
    <foreach collection="list" item="item" index="index" open="" close="" separator=";">
    INSERT INTO test_user (id,username,password,remark)
    VALUES (#{item.id},#{item.username},#{item.password},#{item.remark})
    </foreach>
    </insert>
  • Java代码,生成1000条数据

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    @RequestMapping(value = "batchAdd", method = RequestMethod.GET)
    @ResponseBody
    public Object batchAdd() {
    long start = System.currentTimeMillis();
    List<User> list = new ArrayList<User>();
    for (int i = 1; i < 1000; i++) {
    User user = new User();
    user.setId(null);
    user.setUsername(String.valueOf(i));
    user.setPassword(String.valueOf(i));
    list.add(user);
    }
    userService.batchAdd(list);
    long end = System.currentTimeMillis();
    String result = "foreache耗时:" + (end - start) + "ms";
    System.out.println(result);
    return result;
    }

耗时

43444ms

2. 使用foreach,开始事务拦截

使用1中的代码,不同的是开启事务拦截,拦截add()方法

1
2
3
4
5
6
<tx:advice id="txAdvice" transaction-manager="txManager">
<tx:attributes>
<tx:method name="save*" propagation="REQUIRED"/>
<tx:method name="add*" propagation="REQUIRED" />
</tx:attributes>
</tx:advice>

执行耗时

1001ms

3. 在java代码中进行循环,多线程提交

sqlMap


INSERT INTO test_user (id,username,password,remark)
VALUES (#{id},#{username},#{password},#{remark})

java代码

每生成一条数据立即提交到线程池

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
@RequestMapping(value = "forAdd", method = RequestMethod.GET)
@ResponseBody
public Object forAdd() throws Exception {
ThreadPoolExecutor pool = new ThreadPoolExecutor(300, Integer.MAX_VALUE,
10000L, TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(10000));
List<Future> flist = new ArrayList<Future>();
long start = System.currentTimeMillis();
for (int i = 1; i < 1000; i++) {
User user = new User();
user.setId(null);
user.setUsername(String.valueOf(i));
user.setPassword(String.valueOf(i));
Future future = pool.submit(new AddUserTask(userService, user));
flist.add(future);
}
for (Future future : flist) {
future.get();
}
long end = System.currentTimeMillis();
String result = "java中for循环耗时: " + (end - start) + "ms";
System.out.println(result);
return result;
}

耗时

2404ms

同时,观察数据库连接数,发现了大量连接

4.结论

使用MyBatis foreach,开启事务。一次性提交,执行最快

使用多线程提交,速度比较快,但是会消耗消耗较多的服务器资源和连接

使用MyBatis foreach,不开启事务。相当于是单线程把sql顺序发动到数据库,执行时间最长。

建议

建议使用事务进行提交
如果数据量比较大,建议把数据处理成多组,然后每组使用事务进提交,防止事务过大