• 欢迎访问搞代码网站,推荐使用最新版火狐浏览器和Chrome浏览器访问本网站!
  • 如果您觉得本站非常有看点,那么赶紧使用Ctrl+D 收藏搞代码吧

MyBatis批量插入的几种方式效率比较

java 搞代码 4年前 (2022-01-05) 49次浏览 已收录 0个评论
文章目录[隐藏]

最近工作中遇到了解析excel,然后批量插入,发现这个插入时间比较长,所以想要进行一些优化,下面这篇文章主要给大家介绍了关于MyBatis批量插入的几种方式效率比较的相关资料,需要的朋友可以参考下

前言

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

  • 反复执行单条插入语句
  • foreach 拼接 sql
  • 批处理

一、前期准备

基于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 list); } 

1.5 UserInfoBatchMapper.xml

    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 into user_info_batch (user_name, pass_word, nick_name, mobile, email, gmt_create, gmt_update) values  (#{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}) 

1.6 预备数据

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

 private List list = new ArrayList(); private List lessList = new ArrayList(); private List lageList = new ArrayList(); private List warmList = new ArrayList(); // 计数工具 private StopWatch sw = new StopWatch(); 

为了方便组装数据,抽出了一个公共方法。

 private List assemblyData(int count){ List 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(1<strong style="color:transparent">来源gaodaima#com搞(代@码网</strong>000000); 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 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。

四、批处理

该方式 mapper 和xml 复用了 2.1。

4.1 rewriteBatchedStatements 参数

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

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

  • rewriteBatchedStatements参数介绍

MySql的JDBC连接的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批量插入的几种方式效率比较的详细内容,更多请关注gaodaima搞代码网其它相关文章!


搞代码网(gaodaima.com)提供的所有资源部分来自互联网,如果有侵犯您的版权或其他权益,请说明详细缘由并提供版权或权益证明然后发送到邮箱[email protected],我们会在看到邮件的第一时间内为您处理,或直接联系QQ:872152909。本网站采用BY-NC-SA协议进行授权
转载请注明原文链接:MyBatis批量插入的几种方式效率比较

喜欢 (0)
[搞代码]
分享 (0)
发表我的评论
取消评论

表情 贴图 加粗 删除线 居中 斜体 签到

Hi,您需要填写昵称和邮箱!

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址