批处理数据主要有三种方式:

  1. 反复执行单条插入语句
  2. foreach 拼接 sql
  3. 批处理

一、前期准备

基于Spring Boot + Mysql,同时为了省略get/set,使用了lombok,详见pom.xml

1.1 表结构

id 使用数据库自增。

DROP TABLE IF EXISTS `user_info_batch`;
CREATE TABLE `user_info_batch` (
`id` bigint(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键id',
`user_name` varchar(100) NOT NULL COMMENT '账户名称',
`pass_word` varchar(100) NOT NULL COMMENT '登录密码',
`nick_name` varchar(30) NOT NULL COMMENT '昵称',
`mobile` varchar(30) NOT NULL COMMENT '手机号',
`email` varchar(100) DEFAULT NULL COMMENT '邮箱地址',
`gmt_create` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`gmt_update` timestamp NULL DEFAULT NULL COMMENT '更新时间',
PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8 ROW_FORMAT=DYNAMIC COMMENT 'Mybatis Batch';

1.2 项目配置文件

细心的你可能已经发现,数据库url 后面跟了一段 rewriteBatchedStatements=true,有什么用呢?先不急,后面会介绍。

# 数据库配置
spring:
datasource:
url: jdbc:mysql://47.111.118.152:3306/mybatis?rewriteBatchedStatements=true
username: mybatis
password: password
driver-class-name: com.mysql.cj.jdbc.Driver
# mybatis
mybatis:
mapper-locations: classpath:mapper/*.xml
type-aliases-package: cn.van.mybatis.batch.entity

1.3 实体类

@Data
@Accessors(chain = true)
public class UserInfoBatchDO implements Serializable {
private Long id; private String userName; private String passWord; private String nickName; private String mobile; private String email; private LocalDateTime gmtCreate; private LocalDateTime gmtUpdate;
}

1.4 UserInfoBatchMapper

public interface UserInfoBatchMapper {

    /** 单条插入
* @param info
* @return
*/
int insert(UserInfoBatchDO info); /**
* foreach 插入
* @param list
* @return
*/
int batchInsert(List<UserInfoBatchDO> list);
}

1.5 UserInfoBatchMapper.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="cn.van.mybatis.batch.mapper.UserInfoBatchMapper"> <insert id="insert" parameterType="cn.van.mybatis.batch.entity.UserInfoBatchDO">
insert into user_info_batch (user_name, pass_word, nick_name, mobile, email, gmt_create, gmt_update)
values (#{userName,jdbcType=VARCHAR}, #{passWord,jdbcType=VARCHAR},#{nickName,jdbcType=VARCHAR}, #{mobile,jdbcType=VARCHAR}, #{email,jdbcType=VARCHAR}, #{gmtCreate,jdbcType=TIMESTAMP}, #{gmtUpdate,jdbcType=TIMESTAMP})
</insert> <insert id="batchInsert">
insert into user_info_batch (user_name, pass_word, nick_name, mobile, email, gmt_create, gmt_update)
values
<foreach collection="list" item="item" separator=",">
(#{item.userName,jdbcType=VARCHAR}, #{item.passWord,jdbcType=VARCHAR}, #{item.nickName,jdbcType=VARCHAR}, #{item.mobile,jdbcType=VARCHAR}, #{item.email,jdbcType=VARCHAR}, #{item.gmtCreate,jdbcType=TIMESTAMP}, #{item.gmtUpdate,jdbcType=TIMESTAMP})
</foreach>
</insert>
</mapper>

1.6 预备数据

为了方便测试,抽离了几个变量,并进行提前加载。

    private List<UserInfoBatchDO> list = new ArrayList<>();
private List<UserInfoBatchDO> lessList = new ArrayList<>();
private List<UserInfoBatchDO> lageList = new ArrayList<>();
private List<UserInfoBatchDO> warmList = new ArrayList<>();
// 计数工具
private StopWatch sw = new StopWatch();
  • 为了方便组装数据,抽出了一个公共方法。
    private List<UserInfoBatchDO> assemblyData(int count){
List<UserInfoBatchDO> list = new ArrayList<>();
UserInfoBatchDO userInfoDO;
for (int i = 0;i < count;i++){
userInfoDO = new UserInfoBatchDO()
.setUserName("Van")
.setNickName("风尘博客")
.setMobile("17098705205")
.setPassWord("password")
.setGmtUpdate(LocalDateTime.now());
list.add(userInfoDO);
}
return list;
}
  • 预热数据
    @Before
public void assemblyData() {
list = assemblyData(200000);
lessList = assemblyData(2000);
lageList = assemblyData(1000000);
warmList = assemblyData(5);
}

二、反复执行单条插入语句

可能‘懒’的程序员会这么做,很简单,直接在原先单条insert语句上嵌套一个for循环。

2.1 对应 mapper 接口

int insert(UserInfoBatchDO info);

2.2 测试方法

因为这种方法太慢,所以数据降低到 2000

@Test
public void insert() {
log.info("【程序热身】");
for (UserInfoBatchDO userInfoBatchDO : warmList) {
userInfoBatchMapper.insert(userInfoBatchDO);
}
log.info("【热身结束】");
sw.start("反复执行单条插入语句");
// 这里插入 20w 条太慢了,所以我只插入了 2000 条
for (UserInfoBatchDO userInfoBatchDO : lessList) {
userInfoBatchMapper.insert(userInfoBatchDO);
}
sw.stop();
log.info("all cost info:{}",sw.prettyPrint());
}

2.3 执行时间

  • 第一次
-----------------------------------------
ms % Task name
-----------------------------------------
59887 100% 反复执行单条插入语句
  • 第二次
-----------------------------------------
ms % Task name
-----------------------------------------
64853 100% 反复执行单条插入语句
  • 第三次
-----------------------------------------
ms % Task name
-----------------------------------------
58235 100% 反复执行单条插入语句

该方式插入2000 条数据,执行三次的平均时间:60991 ms

三、foreach 拼接SQL

3.1 对应mapper 接口

int batchInsert(List<UserInfoBatchDO> list);

3.2 测试方法

该方式和下一种方式都采用20w条数据测试。

@Test
public void batchInsert() {
log.info("【程序热身】");
for (UserInfoBatchDO userInfoBatchDO : warmList) {
userInfoBatchMapper.insert(userInfoBatchDO);
}
log.info("【热身结束】");
sw.start("foreach 拼接 sql");
userInfoBatchMapper.batchInsert(list);
sw.stop();
log.info("all cost info:{}",sw.prettyPrint());
}

3.3 执行时间

  • 第一次
-----------------------------------------
ms % Task name
-----------------------------------------
18835 100% foreach 拼接 sql
  • 第二次
-----------------------------------------
ms % Task name
-----------------------------------------
17895 100% foreach 拼接 sql
  • 第三次
-----------------------------------------
ms % Task name
-----------------------------------------
19827 100% foreach 拼接 sql

该方式插入20w 条数据,执行三次的平均时间:18852 ms

四、批处理

该方式 mapperxml 复用了 2.1

4.1 rewriteBatchedStatements 参数

我在测试一开始,发现改成 Mybatis Batch提交的方法都不起作用,实际上在插入的时候仍然是一条条记录的插,而且速度远不如原来 foreach 拼接SQL的方法,这是非常不科学的。

后来才发现要批量执行的话,连接URL字符串中需要新增一个参数:rewriteBatchedStatements=true

  • rewriteBatchedStatements参数介绍

MySqlJDBC连接的url中要加rewriteBatchedStatements参数,并保证5.1.13以上版本的驱动,才能实现高性能的批量插入。MySql JDBC驱动在默认情况下会无视executeBatch()语句,把我们期望批量执行的一组sql语句拆散,一条一条地发给MySql数据库,批量插入实际上是单条插入,直接造成较低的性能。只有把rewriteBatchedStatements参数置为true, 驱动才会帮你批量执行SQL。这个选项对INSERT/UPDATE/DELETE都有效。

4.2 批处理准备

  • 手动注入 SqlSessionFactory
    @Resource
private SqlSessionFactory sqlSessionFactory;
  • 测试代码
@Test
public void processInsert() {
log.info("【程序热身】");
for (UserInfoBatchDO userInfoBatchDO : warmList) {
userInfoBatchMapper.insert(userInfoBatchDO);
}
log.info("【热身结束】");
sw.start("批处理执行 插入");
// 打开批处理
SqlSession session = sqlSessionFactory.openSession(ExecutorType.BATCH);
UserInfoBatchMapper mapper = session.getMapper(UserInfoBatchMapper.class);
for (int i = 0,length = list.size(); i < length; i++) {
mapper.insert(list.get(i));
//每20000条提交一次防止内存溢出
if(i%20000==19999){
session.commit();
session.clearCache();
}
}
session.commit();
session.clearCache();
sw.stop();
log.info("all cost info:{}",sw.prettyPrint());
}

4.3 执行时间

  • 第一次
-----------------------------------------
ms % Task name
-----------------------------------------
09346 100% 批处理执行 插入
  • 第二次
-----------------------------------------
ms % Task name
-----------------------------------------
08890 100% 批处理执行 插入
  • 第三次
-----------------------------------------
ms % Task name
-----------------------------------------
09042 100% 批处理执行 插入

该方式插入20w 条数据,执行三次的平均时间:9092 ms

4.4 如果数据更大

当我把数据扩大到 100w 时,foreach 拼接 sql 的方式已经无法完成插入了,所以我只能测试批处理的插入时间。

测试时,仅需将 【4.2】测试代码中的 list 切成 lageList 测试即可。

  • 第一次
-----------------------------------------
ms % Task name
-----------------------------------------
32419 100% 批处理执行 插入
  • 第二次
-----------------------------------------
ms % Task name
-----------------------------------------
31935 100% 批处理执行 插入
  • 第三次
-----------------------------------------
ms % Task name
-----------------------------------------
33048 100% 批处理执行 插入

该方式插入100w 条数据,执行三次的平均时间:32467 ms

五、总结

批量插入方式 数据量 执行三次的平均时间
循环插入单条数据 2000 60991 ms
foreach 拼接sql 20w 18852 ms
批处理 20w 9092 ms
批处理 100w 32467 ms
  1. 循环插入单条数据虽然效率极低,但是代码量极少,数据量较小时可以使用,但是数据量较大禁止使用,效率太低了;
  2. foreach 拼接sql的方式,使用时有大段的xml和sql语句要写,很容易出错,虽然效率尚可,但是真正应对大量数据的时候,依旧无法使用,所以不推荐使用;
  3. 批处理执行是有大数据量插入时推荐的做法,使用起来也比较方便。

本文示例代码

【MyBatis】几种批量插入效率的比较的更多相关文章

  1. mybatis三种批量插入方式对比

    <insert id="addInquiryQA" parameterType="java.util.List"> insert into inqu ...

  2. Mybatis与JDBC批量插入MySQL数据库性能测试及解决方案

    转自http://www.cnblogs.com/fnz0/p/5713102.html 不知道自己什么时候才有这种钻研精神- -. 1      背景 系统中需要批量生成单据数据到数据库表,所以采用 ...

  3. MyBatis 使用 foreach 批量插入

    MyBatis 使用 foreach 批量插入 参考博文 老司机学习MyBatis之动态SQL使用foreach在MySQL中批量插入 使用MyBatis一次性插入多条数据时候可以使用 <for ...

  4. mybatis oracle mysql 批量插入时的坑爹问题--需谨记

    mybatis oracle mysql 批量插入一.oracle的批量插入方式insert into db(id, zgbh, shbzh) select '1', '2', '3' from du ...

  5. sqlserver 下三种批量插入数据的方法

    本文将介绍三种批量插入数据的方法,需要的朋友可以参考下 本文将介绍三种批量插入数据的方法.第一种方法是使用循环语句逐个将数据项插入到数据库中:第二种方法使用的是SqlBulkCopy,使您可以用其他源 ...

  6. 关于 MyBatis MyBatis-Spring Jdbc 批量插入的各种比较分析

    因为目前SME项目中编写了一套蜘蛛爬虫程序,所以导致插入数据库的数据量剧增.就项目中使用到的3种DB插入方式进行了一个Demo分析: 具体代码如下: 1: MyBatis 开启Batch方式,最普通的 ...

  7. mybatis使用序列批量插入数据

    mybatis只提供了单条数据的插入,要批量插入数据我们可以使用循环一条条的插入,但是这样做的效率太低下,每插入一条数据就需要提交一次,如果数据量几百上千甚至更多,插入性能往往不是我们能接受的,如下例 ...

  8. MyBatis操作Oracle批量插入 ORA-00933: SQL 命令未正确结束

    最近在使用MyBatis操作Oracle数据库的时候,进行批量插入数据,思路是封装一个List集合通过Myabtis 的foreach标签进行循环插入,可是搬照Mysql的批量插入会产生 异常 ### ...

  9. mybatis父子表批量插入

    <!--父子表批量插入 --> <insert id="insertBatch" parameterType="com.niwopay.dto.beni ...

随机推荐

  1. 题解 P6688 可重集

    己所欲者,杀而夺之,亦同天赐 解题思路 一定不要用自动溢出的 Hash!!!!!!! 我真的是调吐了... 思路非常简单明了 : 需要我们创新一下 Hash. 首先我们的 Hash 要满足无序性.. ...

  2. 为ScrollView增加圆角的三种方式,及自定义属性【在Linearlayout中新增ScrollView支持滚动 后续】

    获取圆角的几种方案如下:方案一:通过shape来实现,给scrollView增加背景来实现方案二:通过自定义ScrollView,还要自定义属性,在dispatchDraw中不停的裁剪方案三:用And ...

  3. C++ 读 ,写 文件

    1 //文件操作 2 //文本文件 读 ,写文件 3 4 #include <iostream> 5 #include <string> 6 #include<fstre ...

  4. OEM 刷新配置方法

    一:设置>添加目标>配置自动搜索 二:主机上的目标>针对所选的主机 三:禁用调度 四:设置>添加目标>自动搜索结果 五:主机上的目标,搜索结果 六:删除 七:选择机器数据 ...

  5. 6.算法竞赛中的常用JAVA API :Math类(转载)

    6.算法竞赛中的常用JAVA API :Math类 求最值 最小值 Math.min(int a, int b) Math.min(float a, float b) Math.min(double ...

  6. 快速理解VLAN与三层交换机

    一.VLAN 1.1.VLAN的概述与优势 VLAN是逻辑隔离的虚拟局域网,作用是分割广播域(分为物理分割和逻辑分割) VLAN的优势:控制广播.增强网络安全性.简化网络管理 1.2.VLAN的种类 ...

  7. maven的setting配置远程仓库

    maven的setting,配置远程库. <mirror> <id>nexus-aliyun</id> <mirrorOf>central</mi ...

  8. Python对系统数据进行采集监控——psutil

    大家好,我是辰哥- 今天给大家介绍一个可以获取当前系统信息的库--psutil 利用psutil库可以获取系统的一些信息,如cpu,内存等使用率,从而可以查看当前系统的使用情况,实时采集这些信息可以达 ...

  9. python-scrapy框架学习

    Scrapy框架 Scrapy安装 正常安装会报错,主要是两个原因 0x01 升级pip3包 python -m pip install -U pip 0x02 手动安装依赖 需要手动安装 wheel ...

  10. DVWA-全等级文件包含

    DVWA简介 DVWA(Damn Vulnerable Web Application)是一个用来进行安全脆弱性鉴定的PHP/MySQL Web应用,旨在为安全专业人员测试自己的专业技能和工具提供合法 ...