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

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 左右。

最新文章

  1. Orchard 微软CMS项目介绍
  2. php递归读取目录
  3. [JavaEE]Java NIO原理图文分析及代码实现
  4. 【我所理解的Cocos2d-x】第六章 精灵Sprite 读书笔记
  5. css3控制内容的可选择性
  6. 找出图像I的代数中心
  7. sql - 面试
  8. cocos2dx移植android平台
  9. 伪 alter 弹窗 +弹窗统一
  10. ubuntu虚拟机和主机互ping及secureCRT使用
  11. Leetcode_102_Binary Tree Level Order Traversal
  12. MyBatis 处理关系运算符
  13. js对重复数组去重
  14. Power Network POJ - 1459 网络流 DInic 模板
  15. 使用Node.js+Hexo+Github搭建个人博客(续)
  16. CentOS7通过rsync+crontab实现两台服务器文件同步
  17. JS控制音频顺序播放
  18. SQL-32 将employees表的所有员工的last_name和first_name拼接起来作为Name,中间以一个空格区分
  19. noip2017 PJ AK记
  20. CTF实验吧让我进去writeup

热门文章

  1. Elasticsearch:如何把Elasticsearch中的数据导出为CSV格式的文件
  2. 查看docker容器占用的内存
  3. 几篇关于MySQL数据同步到Elasticsearch的文章---第五篇:logstash-input-jdbc实现mysql 与elasticsearch实时同步深入详解
  4. 银河麒麟安装node,mysql,forever环境
  5. Linux+Wine玩火影忍者究极风暴3指南
  6. TTD 专题 (第一篇):C# 那些短命线程都在干什么?
  7. 新电脑搭建vue项目步凑
  8. C语言小白刷题
  9. 一键上手时下最火AI作画工具
  10. 在电脑主机(MainFrame)中只需要按下主机的开机按钮(on()),即可调用其它硬件设备和软件的启动方法,如内存(Memory)的自检(check())、CPU的运行(run())、硬盘(Hard