mybatis批量插入5000+条数据

客官°小女子只卖身不卖艺 2023-10-08 20:58 141阅读 0赞

近日,项目中有一个耗时较长的Job存在CPU占用过高的问题,经排查发现,主要时间消耗在往MyBatis中批量插入数据。mapper configuration是用foreach循环做的,差不多是这样。

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

乍看上去这个foreach没有问题,但是经过项目实践发现,当表的列数较多(20+),以及一次性插入的行数较多(5000+)时,整个插入的耗时十分漫长,达到了14分钟,这是不能忍的。

查阅资料可知,mybatis默认执行器类型为Simple,会为每个语句创建一个新的预处理语句,也就是创建一个PreparedStatement对象。在我们的项目中,会不停地使用批量插入这个方法,而因为MyBatis对于含有的语句,无法采用缓存,那么在每次调用方法时,都会重新解析sql语句。

因此,耗时就耗在,由于我foreach后有5000+个values,所以这个PreparedStatement特别长,包含了很多占位符,对于占位符和参数的映射尤其耗时。

所以,如果非要使用 foreach 的方式来进行批量插入的话,可以考虑减少一条 insert 语句中 values 的个数。一般按经验来说,一次性插20~50行数量是比较合适的,时间消耗也能接受。

重点来了。上面讲的是,如果非要用的方式来插入,可以提升性能的方式。而实际上,MyBatis文档中写批量插入的时候,是推荐使用另外一种方法。

  1. SqlSession session = sqlSessionFactory.openSession(ExecutorType.BATCH);
  2. try {
  3. SimpleTableMapper mapper = session.getMapper(SimpleTableMapper.class);
  4. List<SimpleTableRecord> records = getRecordsToInsert(); // not shown
  5. BatchInsert<SimpleTableRecord> batchInsert = insert(records)
  6. .into(simpleTable)
  7. .map(id).toProperty("id")
  8. .map(firstName).toProperty("firstName")
  9. .map(lastName).toProperty("lastName")
  10. .map(birthDate).toProperty("birthDate")
  11. .map(employed).toProperty("employed")
  12. .map(occupation).toProperty("occupation")
  13. .build()
  14. .render(RenderingStrategy.MYBATIS3);
  15. batchInsert.insertStatements().stream().forEach(mapper::insert);
  16. session.commit();
  17. } finally {
  18. session.close();
  19. }

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

  1. Connection connection = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/mydb?useUnicode=true&characterEncoding=UTF-8&useServerPrepStmts=false&rewriteBatchedStatements=true","root","root");
  2. connection.setAutoCommit(false);
  3. PreparedStatement ps = connection.prepareStatement(
  4. "insert into tb_user (name) values(?)");
  5. for (int i = 0; i < stuNum; i++) {
  6. ps.setString(1,name);
  7. ps.addBatch();
  8. }
  9. ps.executeBatch();
  10. connection.commit();
  11. connection.close();

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

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

发表评论

表情:
评论列表 (有 0 条评论,141人围观)

还没有评论,来说两句吧...

相关阅读