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

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

记录一次 MyBatis 批量插入的优化-BatchInsert的更多相关文章

  1. 160421、MyBatis批量插入数据

    在程序中封装了一个List集合对象,然后需要把该集合中的实体插入到数据库中,由于项目使用了Spring+MyBatis的配置,所以打算使用MyBatis批量插入,由于之前没用过批量插入,在网上找了一些 ...

  2. oracle+mybatis批量插入踩坑记

    最近在项目中需要使用oracle+mybatis批量插入数据,因为自增主键,遇到问题,现记录如下: 一.常用的两种sql写法报错 1.insert ... values ... <insert ...

  3. mybatis批量插入数据到oracle

    mybatis 批量插入数据到oracle报 ”java.sql.SQLException: ORA-00933: SQL 命令未正确结束“  错误解决方法 oracle批量插入使用 insert a ...

  4. springMVC 接收数组参数,mybatis 接收数组参数,mybatis批量插入/批量删除案例

    案例是给一个用户赋予多个权限,多个权限用其对应的主键 id 为参数,组成了 一个id数组,传给springMVC,然后springMVC传给mybatis,然后mybatis批量插入.其实类似的场景还 ...

  5. 解决Oracle+Mybatis批量插入报错:SQL 命令未正确结束

    Mybatis批量插入需要foreach元素.foreach元素有以下主要属性: (1)item:集合中每一个元素进行迭代时的别名. (2)index:指定一个名字,用于表示在迭代过程中,每次迭代到的 ...

  6. JDBC批量插入数据优化,使用addBatch和executeBatch

    JDBC批量插入数据优化,使用addBatch和executeBatch SQL的批量插入的问题,如果来个for循环,执行上万次,肯定会很慢,那么,如何去优化呢? 解决方案:用 preparedSta ...

  7. MyBatis批量插入数据(MySql)

    由于项目需要生成多条数据,并保存到数据库当中,在程序中封装了一个List集合对象,然后需要把该集合中的实体插入到数据库中,项目使用了Spring+MyBatis,所以打算使用MyBatis批量插入,应 ...

  8. mybatis批量插入oracle时报错:unique constraint (table name) violated

    mybatis批量插入oracle时报错:unique constraint (table name) violated,是因为插入的集合中有两条相同唯一约束的数据.

  9. Mybatis 批量插入数据

    --mybatis 批量插入数据 --1.Oracle(需要测试下是否支持MySQL) < insert id ="insertBatch" parameterType=&q ...

  10. 【mybatis批量插入】

    mybatis批量插入操作: MySQL:1.INSERT INTO TABLE_NAME(ID,NAME)VALUES(1,'张三'),(2,'李四')                  2.INS ...

随机推荐

  1. 应用健康: Liveness 与 Readiness

    文章转载自:https://www.kuboard.cn/learning/k8s-intermediate/workload/pod-health.html 介绍 Liveness 指针是存活指针, ...

  2. 在k8s中部署前后端分离项目进行访问的两种配置方式

    第一种方式 (1) nginx配置中只写前端项目的/根路径配置 前端项目使用的Dockerfile文件内容 把前端项目编译后生成的dist文件夹放在nginx的html默认目录下,浏览器访问前端项目时 ...

  3. filebeat测试output连通性

    在默认的情况下,直接运行filebeat的话,它选择的默认的配置文件是当前目录下的filebeat.yml文件. filebeat.yml文件内容 filebeat.inputs: - type: l ...

  4. 打印 Logger 日志时,需不需要再封装一下工具类?

    在开发过程中,打印日志是必不可少的,因为日志关乎于应用的问题排查.应用监控等.现在打印日志一般都是使用 slf4j,因为使用日志门面,有助于打印方式统一,即使后面更换日志框架,也非常方便.在 < ...

  5. C#.NET ORM 如何访问 Access 数据库 [FreeSql]

    最近很多 .net QQ 群无故被封停,特别是 wpf 群几乎全军覆没.依乐祝的 .net6交流群,晓晨的 .net跨平台交流群,导致很多码友流离失所无家可归,借此机会使用一次召唤术,有需要的请加群: ...

  6. golang channel底层结构和实现

    一.介绍 Golang 设计模式: 不要通过共享内存来通信,而要通过通信实现内存共享 channel是基于通信顺序模型(communication sequential processes, CSP) ...

  7. Spring 深入——IoC 容器 01

    IoC容器的实现学习--01 目录 IoC容器的实现学习--01 简介 IoC 容器系列的设计与实现:BeanFactory 和 ApplicationContext BeanFactory load ...

  8. 如何编写 Pipeline 脚本

    前言 Pipeline 编写较为麻烦,为此,DataKit 中内置了简单的调试工具,用以辅助大家来编写 Pipeline 脚本. 调试 grok 和 pipeline 指定 pipeline 脚本名称 ...

  9. pytorch 环境配置

    一.下载Anaconda 二.添加清华镜像 # 添加清华镜像 conda config --add channels https://mirrors.tuna.tsinghua.edu.cn/anac ...

  10. 快速创建软件安装包-ClickOnce

    大家好,我是沙漠尽头的狼. .NET是免费,跨平台,开源,用于构建所有应用的开发人员平台. 今天介绍使用ClickOnce制作软件安装包,首先我们先了解什么是ClickOne. 1. 什么是Click ...