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

  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. CreatFile打开驱动失败

    使用  CreateFile(DRIVER_PATH, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ, 0, OPEN_EXISTING, FILE_AT ...

  2. TypeScript学习笔记(三)泛型、模块化和命名空间

    目录 一.泛型 1. 泛型函数 2. 泛型类 3. 泛型接口 写法一 写法二 两种写法的区别 二.模块化 1. export写法一 2. export写法二 3. 为引入的方法或变量起别名 4. ex ...

  3. Js实现随机某个li样式增加

    一.首先引入jquery  cdn   二.基础样式 三.目的 为了使随机某个li背后有个旋转的图片 四.核心代码 html代码: <div class="bg3"> ...

  4. 双非本科Android开发,如何逆袭拿到大厂 Offer?

    从2020年3月18日投出第一份暑期实习简历至今,已经过去400多天.我也尘埃落定,即将去CVTE做Android开发. 休息了很长时间,如今已经能够很平静地回首这段历程,写下这篇文,致敬曾经走过的漫 ...

  5. 我,Android开发5年,32岁失业,现实给我狠狠上了一课!

    如今的职场,风险是越来越高,不管你是应届生或者你是否中年,遇到好点的企业,红火那么做个三五年,运气不好,半年甚至2.3个月也就玩完了. 所以,即使你希望工作能稳定,但也会让你大失所望,职场寿命就那么几 ...

  6. Maven 基础标签之版本管理和冲突解决

    前言 我们在做java项目的时候由于jar包太多,我们就需要使用maven做项目管理,管理项目的jar包依赖,包括打包上线 maven基础 Maven 是一个项目管理工具,主要用于项目构建,依赖管理, ...

  7. 一个工业级、跨平台、轻量级的 tcp 网络服务框架:gevent

    前言 作为公司的公共产品,经常有这样的需求:就是新建一个本地服务,产品线作为客户端通过 tcp 接入本地服务,来获取想要的业务能力.与印象中动辄处理成千上万连接的 tcp 网络服务不同,这个本地服务是 ...

  8. MOOC大学计算机课程推荐

    转自:https://zhuanlan.zhihu.com/p/30659834  这个是大佬总结的, 大学计算机课程 以下课程是我在MOOC上找到的所有我认为讲的好的. 主要是中国大学MOOC,学堂 ...

  9. Linux 开机关机在线求助与指令输入

    由于那本书版本稍稍有点早,我就跳过学习第二章第三章了(分别是主机规划和虚拟机安装)下图为在自己电脑上安装好的redhat7 4.1 我们来登入 其中第一行是Linux发行商和发行版本(欸,我的这个怎么 ...

  10. 🏆【JVM技术专区】「编译技术专题」带你彻底认识Java的编译技术

    前提概要 Java的class字节码并不是机器语言,要想让机器能够执行,还需要把字节码翻译成机器指令.这个过程是Java虚拟机做的,这个过程也叫编译.是更深层次的编译. 在编译原理中,把源代码翻译成机 ...