首页 > 其他分享 >记录一次 MyBatis 批量插入的优化-BatchInsert

记录一次 MyBatis 批量插入的优化-BatchInsert

时间:2022-12-06 14:57:08浏览次数:72  
标签:语句 insert BatchInsert 批量 插入 foreach statement MyBatis

记录在一次项目问题排查过程中,遇到在数据量大的情况下,向数据库批量插入非常耗时长的问题。

1、分析

首先,代码是在 service 中,采用的是 for 循环调用 insert 语句的方式:

for(int i =0; i < list.size(); i++) {
    baseMapper.insert(list.get(i));
}

此代码的实际执行 sql 就是一个个 insert 语句

2、优化过程

在 Mysql Docs 中,提到过这种情况,如果优化插入速度,可以将多个小型操作组合到一个大型操作中。

就是在 service 层只调用一次,在 mapper 中进行循环

mapper 中

<insert id="batchInsert" parameterType="java.util.List">
    insert into USER (id, name) values
    <foreach collection="list" item="model" index="index" separator=",">
        (#{model.id}, #{model.name})
    </foreach>
</insert>

这样执行,相当于在单个连接中,执行一个 insert 语句,在一定程度上有很好的优化效果。

但是此此操作依然存在限制。经过项目实践,当表的列数比较多(20+),以及一次性插入的行数较多(5000+)时,整个插入的耗时十分漫长,达到了14分钟。


查阅资料可以发现

Insert inside Mybatis foreach is not batch, this is a single (could become giant) SQL statement and that brings drawbacks:

  • some database such as Oracle here does not support.
  • in relevant cases: there will be a large number of records to insert and the database configured limit (by default around 2000 parameters per statement) will be hit, and eventually possibly DB stack error if the statement itself become too large.

Iteration over the collection must not be done in the mybatis XML. Just execute a simple Insertstatement in a Java Foreach loop. The most important thing is the session Executor type.

Unlike default ExecutorType.SIMPLE, the statement will be prepared once and executed for each record to insert.

  • 从资料中可知,默认执行器类型为Simple,会为每个语句创建一个新的预处理语句,也就是创建一个PreparedStatement对象。

  • 在我们的项目中,会不停地使用批量插入这个方法,而因为MyBatis对于含有<foreach>的语句,无法采用缓存,那么在每次调用方法时,都会重新解析sql语句。

Internally, it still generates the same single insert statement with many placeholders as the JDBC code above.

MyBatis has an ability to cache PreparedStatement, but this statement cannot be cached because it contains <foreach /> element and the statement varies depending on the parameters. As a result, MyBatis has to 1) evaluate the foreach part and 2) parse the statement string to build parameter mapping [1] on every execution of this statement.

And these steps are relatively costly process when the statement string is big and contains many placeholders.

[1] simply put, it is a mapping between placeholders and the parameters.

从上述资料可知,耗时就耗在,由于我foreach后有5000+个values,所以这个PreparedStatement特别长,包含了很多占位符,对于占位符和参数的映射尤其耗时。并且,查阅相关资料可知,values的增长与所需的解析时间,是呈指数型增长的。

  • 所以如果使用 foreach 的方式插入,可以将数据进行分页,分批插入,一次插入20-50条数据。

而在 MyBatis 官网,是有另一种优化方案的,可以参考地址 http://www.mybatis.org/mybatis-dynamic-sql/docs/insert.html 中 Batch Insert Support 标题里的内容

即基本思想是将 MyBatis session 的 executor type 设为 Batch ,然后多次执行插入语句。就类似于JDBC的下面语句一样。

3、总结

  • 经过试验,使用了 ExecutorType.BATCH 的插入方式,性能显著提升,不到 2s 便能全部插入完成。

  • 总结一下,如果MyBatis需要进行批量插入,推荐使用 ExecutorType.BATCH 的插入方式,如果非要使用 的插入的话,需要将每次插入的记录控制在 20~50 左右。

标签:语句,insert,BatchInsert,批量,插入,foreach,statement,MyBatis
From: https://www.cnblogs.com/xiangningdeguang/p/16955191.html

相关文章