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

<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 INTO `table1` (`field1`, `field2`) VALUES ("data1", "data2");
INSERT INTO `table1` (`field1`, `field2`) VALUES ("data1", "data2");
INSERT INTO `table1` (`field1`, `field2`) VALUES ("data1", "data2");
INSERT INTO `table1` (`field1`, `field2`) VALUES ("data1", "data2");
INSERT INTO `table1` (`field1`, `field2`) VALUES ("data1", "data2");

转化为:

INSERT INTO `table1` (`field1`, `field2`) VALUES ("data1", "data2"),("data1", "data2"),("data1", "data2"),("data1", "data2"),("data1", "data2");

在[MySql Docs][]中也提到过这个trick,如果要优化插入速度时,可以将许多小型操作组合到一个大型操作中。理想情况下,这样可以在单个连接中一次性发送许多新行的数据,并将所有索引更新和一致性检查延迟到最后才进行。

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

Of course don't combine ALL of them, if the amount is HUGE. Say you have 1000 rows you need to insert, then don't do it one at a time. You shouldn't equally try to have all 1000 rows in a single query. Instead break it into smaller sizes.

它强调,当插入数量很多时,不能一次性全放在一条语句里。可是为什么不能放在同一条语句里呢?这条语句为什么会耗时这么久呢?我查阅了[资料][Link 2]发现:

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.

  SqlSession session = sessionFactory.openSession(ExecutorType.BATCH);for (Model model : list) {session.insert("insertStatement", model);}session.flushStatements();

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

从[资料][Link 3]中可知,默认执行器类型为Simple,会为每个语句创建一个新的预处理语句,也就是创建一个PreparedStatement对象。在我们的项目中,会不停地使用批量插入这个方法,而因为MyBatis对于含有的语句,无法采用缓存,那么在每次调用方法时,都会重新解析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.

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

![43_1.png][43_1.png]

所以,如果非要使用 foreach 的方式来进行批量插入的话,可以考虑减少一条 insert 语句中 values 的个数,最好能达到上面曲线的最底部的值,使速度最快。一般按[经验][Link 6]来说,一次性插20~50行数量是比较合适的,时间消耗也能接受。

重点来了。上面讲的是,如果非要用的方式来插入,可以提升性能的方式。而实际上,MyBatis文档中写批量插入的时候,是推荐使用另外一种方法。(可以看 [http://www.mybatis.org/mybatis-dynamic-sql/docs/insert.html][http_www.mybatis.org_mybatis-dynamic-sql_docs_insert.html] 中 Batch Insert Support 标题里的内容)

SqlSession session = sqlSessionFactory.openSession(ExecutorType.BATCH);
try {SimpleTableMapper mapper = session.getMapper(SimpleTableMapper.class);List<SimpleTableRecord> records = getRecordsToInsert(); // not shownBatchInsert<SimpleTableRecord> batchInsert = insert(records).into(simpleTable).map(id).toProperty("id").map(firstName).toProperty("firstName").map(lastName).toProperty("lastName").map(birthDate).toProperty("birthDate").map(employed).toProperty("employed").map(occupation).toProperty("occupation").build().render(RenderingStrategy.MYBATIS3);batchInsert.insertStatements().stream().forEach(mapper::insert);session.commit();
} finally {session.close();
}

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

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

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

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

MyBatis批量插入几千条数据,慎用Foreach相关推荐

  1. MyBatis批量插入几千条数据慎用foreach

    https://blog.csdn.net/huanghanqian/article/details/83177178

  2. mybatis批量插入10万条数据的优化过程

    在使用mybatis插入大量数据的时候,为了提高效率,放弃循环插入,改为批量插入,mapper如下: package com.lcy.service.mapper;import com.lcy.ser ...

  3. 教你急速快速批量插入1000万条数据到mysql数据库表面试题

    急速快速批量插入1000万条数据到mysql数据库表面试题 教你急速快速批量插入1000万条数据到mysql数据库表&面试题 我用到的数据库为,mysql数据库5.7版本的 1.首先自己准备好 ...

  4. 向Mysql批量插入50万条数据

    >批量插入数据脚本 表Sql: CREATE TABLE dept( id INT UNSIGNED PRIMARY KEY AUTO_INCREMENT, deptno MEDIUMINT U ...

  5. MyBatis批量插入(sqlserver BULK INSERT)

    MyBatis批量插入: 1. foreach方式 2.sqlsession + sqlsession.flushStatements方式: //        SqlSession sqlSessi ...

  6. MyBatis批量插入大量数据

    1. 思路分析 批量插入这个问题,我们用 JDBC 操作,其实就是两种思路吧: 用一个 for 循环,把数据一条一条的插入(这种需要开启批处理). 生成一条插入 sql,类似这种 insert int ...

  7. Java怎么实现几十万条数据插入(30万条数据插入MySQL仅需13秒)

    本文主要讲述通过MyBatis.JDBC等做大数据量数据插入的案例和结果. 30万条数据插入插入数据库验证 实体类.mapper和配置文件定义 User实体 mapper接口 mapper.xml文件 ...

  8. 【SpringBoot项目中使用Mybatis批量插入百万条数据】

    SpringBoot项目中使用Mybatis批量插入百万条数据 话不多说,直接上代码,测试原生批处理的效率 开始测试 背景:因为一些业务问题,需要做多数据源,多库批量查询.插入操作,所以就研究了一下. ...

  9. 公司新来个同事,MyBatis批量插入10w条数据仅用2秒,拍案叫绝!

    批量插入功能是我们日常工作中比较常见的业务功能之一,今天咱们来一个 MyBatis 批量插入的汇总篇,同时对 3 种实现方法做一个性能测试,以及相应的原理分析. 先来简单说一下 3 种批量插入功能分别 ...

最新文章

  1. 绘制你的世界:探索构图和真实的深度感
  2. HDU-1394-Minimum Inversion Number
  3. 熟悉mysql基本数据库操作系统_MySQL数据库的基本操作
  4. 连接mysql报zone时区错误
  5. 在windows下安装Redis
  6. sublime中编译的sass如何改变css输出风格?【这里有答案】
  7. 山东大学有人陪!真的吗?_10,000小时! 您真的需要那么多吗?
  8. Java中String.split和StringUtils.split性能比较
  9. ARTetris-AR版俄罗斯方块的源码解析
  10. 镜像数据库上SQL Server复制
  11. java 广告插件_徒手创建一个chrome扩展-屏蔽广告插件
  12. C++ sizeof操作符的用法和strlen函数的区别
  13. docker 阿里镜像加速
  14. firefox 2.0版如何自己制作绿色版?
  15. python 科研作图_科研作图软件(11种)
  16. win10安装PyPESQ库
  17. vuex报错Computed property “xxx“ was assigned to but it has no setter.
  18. faiss 相似特征向量搜索
  19. python程序设计是什么专业-那门用Python讲授的程序设计课程能带给学生什么?
  20. 阿里云服务器选择地域教程

热门文章

  1. Ip camera(网络摄像头)试用
  2. 脚本记录:移动端图片扩大缩小
  3. 云端搭建linux学习环境——手把手教学,赶紧收藏以后使用
  4. 最佳情侣身高差c语言函数,“最佳情侣身高差是多少为妙?”哈哈哈,神评尤为突出啊...
  5. JavaWeb(总+完结)
  6. CSP2021提高组游记
  7. 不要再重复造轮子了,Hutool这款开源工具类库贼好使
  8. 【时间序列聚类】KMedoids聚类+DTW算法
  9. 盛大游戏技术总监徐峥:Unity引擎使用的三种方式
  10. 元宇宙备受关注,Imagination 高性能 GPU 技术将加速元宇宙建设