Mybatis批量插入,形式不同性能也不同

从120s到3s!MyBatis批量插入数据优化分享,那叫一个优雅! 最近在压测一批接口,发现接口处理速度慢的有点超出预期,感觉很奇怪,后面定位发现是数据库批量保存这块很慢。这个项目用的是,批量保存直接用的是提供的 saveBatch。我点进去看了下源码,感觉有点不太对劲:我继续追踪了下,从这个代码来看,确实是 for 循环一条一条执行了,下面的 consumer 执行的就是上面的然后累计一定数量后,一批 flush。从这点来看,这个 saveBach 的性能肯定比直接一条一条 insert 快。我直接进行一个粗略的实验,简单创建了一张表来对比一波! 阅读详情

01 引言

批量插入数据是我们业务中经常遇到的,我们习惯了手搓sql如:

insert into user_info (`name`, `age`, `created_time`) values 
<foreach item ="item" collection="list"  separator=",">
     (#{item.name}, #{item.age}, #{item.createdTime})
</foreach>

这样的脚本,适用于数据量不大的情况,基本满足业务需求,但是却存在安全隐患。当传输的数据量过大时,可能会引起异常。

本节,我们将对比不同的写法以及响应的时间的消耗。

02 日常批量插入

insert into tb (col1, col2) values (val1, val2),......

上面的这种写法确实比一条条的数据入库的速度快太多,我们这里只横向对比不同的批量插入。我们先看看Mysql官方给的Insert执行的流程。

官网地址:https://dev.mysql.com/doc/refman/8.4/en/insert-optimization.html

2.1 安全隐患

所谓的安全隐患,其实就是Mysql的一些配置。而这些配置直接影响sql是否会执行以及执行的效率。

  • bulk_insert_buffer_size:
  • max_allowed_packet

bulk_insert_buffer_size

Myisam引擎参数,在使用myisam引擎执行INSERT ... SELECT,INSERT ... VALUES (...), (...), ...,LOAD DATA INFILE批量插入时,会采用一种缓存树的内存结构来优化插入速度。默认8M。

max_allowed_packet

控制了 MySQL 服务器接收和发送的最大数据包的大小,默认64M。

因为小编测试的Mysql引擎使用的是innodb,所以暂时不考虑第一个参数。小编准备了10W条数据一次全部插入到数据库,就报错了:

所以呢,当一次插入的数据达到一定的数据就会出现异常。

2.2 分批插入

我们通过1W条数据测试一下,不同数据插入的结果:

@Test
public void test01() {
    List<UserInfo> list = new ArrayList<>();
    for (int i = 1; i <= 10000; i++) {
        UserInfo userInfo = new UserInfo();
        userInfo.setName("test" + i);
        userInfo.setAge(new Random().nextInt(1,90));
        userInfo.setCreatedTime(LocalDateTime.now());
        list.add(userInfo);
    }

    List<List<UserInfo>> partition = Lists.partition(list, 5000);
    StopWatch stopWatch = new StopWatch();
    stopWatch.start();
    for (List<UserInfo> userInfos : partition) {
        userInfoMapper.insertBatch(userInfos);
    }
    stopWatch.stop();
    System.out.println("1W 条数据每隔1000插入,耗时:"+stopWatch.getTotalTimeMillis()+"ms");
    // 1W 条数据字节直接插入,耗时:1342ms
    // 1W 条数据每隔500插入,耗时:1270ms
    // 1W 条数据每隔1000插入,耗时,耗时:1257ms
    // 1W 条数据每隔2000插入,耗时,耗时:1142ms
    // 1W 条数据每隔5000插入,耗时,耗时:1153ms
}

脚本

@Insert("""
    <script>
        insert into user_info (`name`, `age`, `created_time`) values 
        <foreach item ="item" collection="list"  separator=",">
             (#{item.name}, #{item.age}, #{item.createdTime})
        </foreach>
    </script>
""")
void insertBatch(@Param("list") List<UserInfo> list);

从直接的结果来看,我们发现:

每次插入2000条左右时,耗时相对比较少。也就是说2000左右时我们每次插入数据的最佳数据量。当然这个和数据库引擎,字段量等有一定关系。

批量插入并不是数据越多越快。

我们可以从stackoverflow上找到类似的答案:

地址:https://stackoverflow.com/questions/19682414/how-can-mysql-insert-millions-records-faster

Redgate上也有人专门做了不同数据量对于批量插入的测试:

https://www.red-gate.com/simple-talk/databases/sql-server/performance-sql-server/comparing-multiple-rows-insert-vs-single-row-insert-with-three-data-load-methods/#main

03 Mybaits-Plus批量插入

Mybaits-Plus简称MP。它本身提供了三种批量插入的方法:

  • insert(Collection<T> entityList, int batchSize)
  • MybatisBatch.Method<?>
  • InsertBatchSomeColumn

3.1 insert

@Test
public void test03() {
    List<UserInfo> list = new ArrayList<>();
    for (int i = 1; i <= 10000; i++) {
        UserInfo userInfo = new UserInfo();
        userInfo.setName("test" + i);
        userInfo.setAge(new Random().nextInt(1,90));
        userInfo.setCreatedTime(LocalDateTime.now());
        list.add(userInfo);
    }

    StopWatch stopWatch = new StopWatch();
    stopWatch.start();
    userInfoMapper.insert(list, 2000);
    stopWatch.stop();
    System.out.println("1W 条数据每隔500插入,耗时:"+stopWatch.getTotalTimeMillis()+"ms");
    // 1W 条数据批量提交插入,耗时:11918ms
    // 1W 条数据每隔500插入,耗时:12856ms
    // 1W 条数据每隔1000插入,耗时:11700ms
    // 1W 条数据每隔2000插入,耗时:12031ms
    // 1W 条数据每隔5000插入,耗时:11745ms
}

我们可以看到对于1W条数据的处理,MP的这种方式是很耗时的。平均在10s以上,但是同样可以得到结论每一批1000条左右时,效果最好。这也是框架默认的分割参数。

慢的原因

关键的代码如下:

源码用了双层for循环,每一条数据都拼接成几个完整的SQL执行,最后提交事务。类似下面的脚本:

START TRANSACTION;

-- 执行一系列SQL语句
insert into tb (col1, col2) values (val1, val2);
insert into tb (col1, col2) values (val1, val2);
-- ......

-- 提交事务
COMMIT;

3.2 MybatisBatch.Method<?>

@Test
public void test05() {
    List<UserInfo> list = new ArrayList<>();
    for (int i = 1; i <= 10000; i++) {
        UserInfo userInfo = new UserInfo();
        userInfo.setName("test" + i);
        userInfo.setAge(new Random().nextInt(1,90));
        userInfo.setCreatedTime(LocalDateTime.now());
        list.add(userInfo);
    }

    List<List<UserInfo>> partition = Lists.partition(list, 5000);
    StopWatch stopWatch = new StopWatch();
    stopWatch.start();
    for (List<UserInfo> userInfos : partition) {
        MybatisBatch<UserInfo> mybatisBatch = new MybatisBatch<>(sqlSessionTemplate.getSqlSessionFactory(), userInfos);
        MybatisBatch.Method<UserInfo> method = new MybatisBatch.Method<>(UserInfoMapper.class);
        mybatisBatch.execute(method.insert());
    }

    stopWatch.stop();
    System.out.println("1W 条数据批量提交插入,耗时:"+stopWatch.getTotalTimeMillis()+"ms");
    // 1W 条数据批量提交插入,耗时:12866ms
    // 1W 条数据每500条批量提交插入,耗时:12624ms
    // 1W 条数据每1000条批量提交插入,耗时:12953ms
    // 1W 条数据每2000条批量提交插入,耗时:12501ms
    // 1W 条数据每5000条批量提交插入,耗时:11670ms
}

3.3 InsertBatchSomeColumn

@Test
public void test06() {
    List<UserInfo> list = new ArrayList<>();
    for (int i = 1; i <= 10000; i++) {
        UserInfo userInfo = new UserInfo();
        userInfo.setName("test" + i);
        userInfo.setAge(new Random().nextInt(1,90));
        userInfo.setCreatedTime(LocalDateTime.now());
        list.add(userInfo);
    }

    List<List<UserInfo>> partition = Lists.partition(list, 5000);
    StopWatch stopWatch = new StopWatch();
    stopWatch.start();
    for (List<UserInfo> userInfos : partition) {
        userInfoMapper.insertBatchSomeColumn(userInfos);
    }
    stopWatch.stop();
    System.out.println("1W 条数据批量提交插入,耗时:"+stopWatch.getTotalTimeMillis()+"ms");
    // 1W 条数据批量提交插入,耗时:1425ms
    // 1W 条数据每500条批量提交插入,耗时:1483ms
    // 1W 条数据每1000条批量提交插入,耗时:1413ms
    // 1W 条数据每2000条批量提交插入,耗时:1492ms
    // 1W 条数据每5000条批量提交插入,耗时:1549ms
}

从运行的结果来看,这种方式才是类似我们手搓的的脚本,效率杠杠的。

04 启用Mybaits的Batch

脚本同2.2中的脚本,仅仅开启Batch属性。

 @Test
public void test07() {
    SqlSession sqlSession = sqlSessionTemplate.getSqlSessionFactory().openSession(ExecutorType.BATCH);
    UserInfoMapper mapper = sqlSession.getMapper(UserInfoMapper.class);
    List<UserInfo> list = new ArrayList<>();
    for (int i = 1; i <= 10000; i++) {
        UserInfo userInfo = new UserInfo();
        userInfo.setName("test" + i);
        userInfo.setAge(new Random().nextInt(1,90));
        userInfo.setCreatedTime(LocalDateTime.now());
        list.add(userInfo);
    }

    List<List<UserInfo>> partition = Lists.partition(list, 5000);
    StopWatch stopWatch = new StopWatch();
    stopWatch.start();
    for (List<UserInfo> userInfos : partition) {
        mapper.insertBatch(userInfos);
    }
    sqlSession.commit();
    stopWatch.stop();
    System.out.println("1W 条数据批量提交插入,耗时:"+stopWatch.getTotalTimeMillis()+"ms");
    // 1W 条数据批量提交插入,耗时:1181ms
    // 1W 条数据每500条批量提交插入,耗时:1201ms
    // 1W 条数据每1000条批量提交插入,耗时:1174ms
    // 1W 条数据每2000条批量提交插入,耗时:1139ms
    // 1W 条数据每5000条批量提交插入,耗时:1184ms
}

开启ExecutorType.BATCH确实比ExecutorType.SIMPLE快了一丢丢。

05 小结

批量插入面对小数据量的时,几乎没有任何区别,可以随意使用。一旦数据量比较大时,我们就需要斟酌,选择合适的插入方法,可以帮我们节省很多资源和时间。

使用rewriteBatchedStatements属性优化Mybatis-Plus批量插入数据 由于项目是使用MyBatis-Plus开发的,用起来也确实比较方便,尤其是service层封装好的一些通用的增删改查方法,省去了不少sql语句的书写,但是在开发过程中,我也发现MyBatis-Plus的saveBatch批量插入方法针对MySQL数据库可能会出现效率贼低的情况,通过不断查询资料发现可以通过加rewriteBatchedStatements参数来解决(保证5.1.13以上版本)。 阅读详情

相关推荐

mybatis+oracle+foreach list 批量插入数据

背景:在list中存放对象,本来可以遍历list得到每一个对象,然后将每个对象插入数据库,但是这样做的话没遍历得到一个对象就要操作一次数据库,如果数据少的话还好,如果是成千上万的数据,就会使开销大大增加和性能大大降低,为了解决这个,就需要将之前的每次插入换成批量插入 直接上sql insert into JK_MATNRDTL_FROM_SRM (ID, RUNSHEE

E=mc² 3329

Mybatis批量插入的四种方式

测试结果还是显而易见的,循环插入>批量插入>BatchType插入>JDBC批量插入

一零贰肆的博客 4万+

MyBatis批量插入的五种方式归纳总结

这里我列举了MyBatis和MyBatis-Plus常用的五种批量插入的方式,进行了详细的总结归纳,写的非常详细,整体思路清晰明了,只分享干货。

黄团团的个人博客 1万+

MyBatis批量插入的五种方式

MyBatis批量插入的五种方式

北执南念的博客 1万+

面试题:MyBatis批量插入的五种方式,哪种最强?

列举了MyBatis和MyBatis-Plus常用的五种批量插入的方式,进行了详细的总结归纳@Override// 注意:此SQL注入器继承了DefaultSqlInjector(默认注入器),调用了DefaultSqlInjector的getMethodList方法,保留了mybatis-plus的自带方法@Bean以上就是我对目前MyBatis常用的批量插入方法的总结。

xuxu96 1003

统信UOS服务器上,达梦DM8数据库安装避坑全记录(附大小写敏感设置)

本文详细介绍了在统信UOS服务器版上部署达梦DM8数据库的全过程,包括环境准备、安装流程、数据库实例初始化及关键参数配置,特别是大小写敏感设置的注意事项。提供了从系统配置到性能优化的完整指南,帮助用户避免常见问题,确保数据库稳定运行。

weixin_30647423的博客 333

巧用MybatisPlus的SQL注入器提升批量插入性能

上一次我们给大家详细讲解 MybatisPlus 的条件构造器wrapper。这次我们来讲另一个开发者需要了解的功能——SQL注入器,并以实战视角讲述如何利用该特性提升MybatisPlus 的批量插入性能

战斧的博客 1万+

避坑指南:解决‘Logical volume contains a filesystem in use’错误,顺利完成LVM根目录扩容

本文详细解析了Linux系统中LVM根目录扩容时遇到的‘Logical volume contains a filesystem in use’错误,提供了从错误诊断到实战解决方案的完整指南。通过动态扩容技术、单用户模式操作和LVM快照等方案,帮助管理员顺利完成根目录扩容,避免数据丢失和系统崩溃。

weixin_28702105的博客 228

解决Mybatis-plus 批量插入太慢的问题,提升插入性能

MyBatis-Plus(简称MP)是一个MyBatis的增强工具,旨在MyBatis的基础上只做增强不做改变,为简化开发、提高效率而生。 特点 无侵入:只做增强不做改变,引入它不会对现有工程产生影响,如丝般顺滑 损耗小:启动即会自动注入基本 CURD,性能基本无损耗,直接面向对象操作 强大的 CRUD 操作:内置通用 Mapper、通用 Service,仅仅通过少量配置即可实现单表大部分 CRUD 操作,更有强大的条件构造器,满足各类使用需求 支持 Lambda 形式

Crystalqy的博客 3万+

unity打包流程整理-Windows/Mac/Linux平台

1.Platform(平台)功能: 选择目标构建的目标平台选项PC, Mac & Linux Standalone: 适用于 Windows、Mac 和 Linux 平台2.Target Platform(目标平台)功能: 选择具体的操作系统版本选项Windows: 针对 Windows 操作系统Mac: 针对 macOS 操作系统Linux: 针对 Linux 操作系统3.Architecture(架构)功能: 选择应用程序的目标处理器架构选项x86: 针对 32 位系统。

weixin_42333247的博客 5141

告别‘线束丛林’:手把手拆解车身域控制器(BCM/PEPS/网关)的硬件选型与PCB布局要点

本文深入解析车身域控制器的硬件选型与PCB布局要点,涵盖主控MCU选择、通信接口设计、电源架构优化及EMC设计等关键环节。通过实战案例分享,帮助工程师解决高密度集成带来的电源完整性、热管理和信号隔离挑战,实现更稳定高效的汽车电子系统设计。

weixin_42567279的博客 396

给你的MyBatis-Plus装上批量插入的翅膀

写在前面:2020年面试必备的Java后端进阶面试题总结了一份复习指南在Github上,内容详细,图文并茂,有需要学习的朋友可以Star一下! GitHub地址:https://github.com/abel-max/Java-Study-Note/tree/master 前言 大家有用过MyBatis-Plus(简称MP)的都知道它是一个MyBatis的增强工具,旨在MyBatis的基础上只做增强不做改变,为简化开发、提高效率而生。 特点 无侵入:只做增强不做改变,引入它不会对现有工程产生影响,如丝般顺

qwe123147369的博客 1万+

ArcGIS笔记13_利用ArcGIS制作岸线与水深地形数据?建立水动力模型之前的数据收集与处理?

在利用MIKE建立水动力模型之前,需要收集、处理和制作诸多数据和资料,主要有岸线数据、水深地形数据、开边界潮位驱动数据、风场数据、潮位和海流观测资料和站点潮汐调和常数资料等。本篇主要介绍这些资料的获取与处理。

晏长街的博客 5949

mybatis批量插入并返回主键(xml和注解两种方式)

mybatis批量插入 在mysql数据库中支持批量插入,所以只要配置useGeneratedKeys和keyProperty就可以批量插入并返回主键了。 比如有个表camera,里面有cameraNo,chanIndex,cameraName这三个字段,其中cameraNo是自增主键。 下面是批量插入的Dao层接口: void batchInsertCameras(@Param("li...

yjclsx的博客 4万+

YOLOv8与Swin Transformer的完美结合:如何提升目标检测的精度与速度

Swin Transformer(Shifted Window Transformer)是一种新型的视觉Transformer架构,采用了局部窗口(Window)和局部窗口偏移(Shifted Window)的策略,极大减少了计算复杂度,同时保持了全局感知能力。局部窗口注意力:每个窗口内的元素进行自注意力计算,减少计算量。窗口偏移:通过在不同层次引入窗口偏移,增强了全局信息的交互。层级设计:通过堆叠多个层次的模块,逐渐捕捉不同尺度的特征。

shrgegrb的博客 2151

最新2026年《网络数据安全风险评估办法》条款解读

大型企业集团IT变革下的零信任安全体系建设方案(55页).pptx。以数据安全为核心的安全立体防御体系解决方案(41页).pptx。让数据安全合规地创造价值(15页 PPT).pptx。大型集团数据安全应急预案(57页).docx。数据安全治理与评估服务业务介绍.pptx。数据安全管理实践(17页).pptx。大数据安全架构设计方案.pptx。大数据安全规划总体方案.pptx。数据库安全综合治理方案.pptx。医疗数据安全建设方案.pptx。数据安全框架规划图.pptx。数据安全治理方案.pptx。

cdfunlove的博客 50

mybatis-plus批量插入方式对比

在我们进行持久化数据操作时,新增数据不仅仅局限于单数据插入。会涉及到需要批量执行数据操作的业务,此时选用不同的批量插入方式会出现插入时间不同,今天我们针对mybatis插件做持久化时批量插入的四种方式做测试对比。 目录 使用标签foreach循环方式 使用 BatchExecutor 批处理执行器进行批量插入 for循环方式执行批量插入数据 MyBatis-Plus 通用IService中saveBatch方式 对比结果 使用标签foreach循环方式 foreach标签经常用于遍

zhou_fan_xi的博客 5063

Quartus II编译时出现Error (119013): Current license file does not support the EP4CE6E22C6 device

Quartus II编译时出现Error (119013): Current license file does not support the EP4CE6E22C6 device, Quartus的安装过程不同于ISE/vivado的安装。“Current license file does not support”这种情况多出现在高版本的quartus中综合很久以前的代码,使用很早型号的FPGA或是CPLD。对于这种情况,就得在官网或是其他网站上下载包含所需Device的库文件。

夜半少年的博客 1万+

MyBatis批量插入数据优化,增加一个参数,效率提升百倍

MyBatis批量插入数据优化,增加一个参数,效率提升百倍,优雅。

java架构师进阶之路的博客 6011

RU DOS版、EFI版32bit/64bit BIOS常用工具

DOS版、EFI版的RU工具,可用来查看IO、MEM、PCI、SMBIOS等资源。亲测在EFI SHELL下可用。

Devexpress16.2.6破解工具

Devexpress16.2.6破解工具Devexpress16.2.6破解工具Devexpress16.2.6破解工具Devexpress16.2.6破解工具

给你的MyBatis-Plus装上批量插入的翅膀,让批量插入的速度飞起来!

作者:ThinkYicnblogs.com/thinkYi/p/13723035.html前言大家有用过MyBatis-Plus(简称MP)的都知道它是一个MyBatis的增强工具,旨...

程序员闪充宝 1840

ET2012新版免狗免安装绿色软件

ET2012新版免狗免安祖昂绿色软件。,新界面,新视觉,

关于mybatis+mysql批量插入的问题

之前在写项目时,会遇到在某个时间点内需要插入多条数据。之前我采用的方式是一次循环插入一条数据,每次插入都是一个过程。1.发起与数据库的链接 2.插入一条数据。 当你需要批量插入成千上万条的时候。就会是这个数量级乘以2,相信大部分的时间是浪费在链接数据库上。  批量插入成千上万条数据,就只需要2个步骤了,链接数据量,插入数据,当然插入数据的时候sql的不同写法,那么执行的效率也会不同,待会我会举

呵呵呵呵呵呵合 6480

复分析 阿尔福斯(Ahlfors) 第三版答案 英文

答案分为十章,有部分习题没有答案,全英文复分析阿尔福斯(Ahlfors) 第三版答案

spring boot + mybatis实现批量插入数据

场景描述:前端以List的形式传入多条待插入数据,调用mybatis-generator自动生成的insert接口一次只能插入一条数据,当然可以写一个for循环一条条插入,但感觉效率太低;所以尝试下一条sql插入多条数据; 解决方案:使用foreach方法来实现多条记录的批量插入 在上一节代码的基础上添加如下代码: 实现方式如下: controller @ApiOperation(value = ...

独钓寒江雪 2万+

给你的 MyBatis-Plus 装上批量插入的翅膀

点击上方 "后端架构师"关注,星标或置顶一起成长后台回复“大礼包”有惊喜礼包!关注订阅号「后端架构师」,收看更多精彩内容每日英文Three things in life...

程序员小乐 3167

mybatis insert 动态生成插入的列及批量插入值

mybatis insert 动态生成插入的列及批量插入值 列名跟表名,当参数传递到mybatis,用${}获取, 不再多说,这里主要说该如何批量插入值 先写一个例子: insert into test(id, name) values(1,"1name"),(2,"2name"),(3,"3name") 思想: 假设:列名是List 形式, 假设columns 列值是List<List> 形式, 假设values 首先列的数量,跟一行的值的数量是相匹配的,再就是列与值在list中的顺序保

潇潇寒的博客 5292

mybatis批量插入并返回批量插入的主键ID

在优化公司系统底层框架批量插入的时候,遇到一个问题,以前的公司底层架构中,批量插入后是返回的插入的条数,而不是返回的批量插入的主键,这样就很难做一些其他的事情,比如如果该表有历史表的话,如给USER_INFO表加一个历史表USER_INFO_HIS CREATE TABLE `USER_INFO` ( `USER_ID` bigint(20) NOT NULL AUTO_INC...

ystyaoshengting的专栏 1283

MyBatis批量插入并返回主键

mybatis批量插入 在mysql数据库中支持批量插入,所以只要配置useGeneratedKeys和keyProperty就可以批量插入并返回主键了。 比如有个表camera,里面有cameraNo,chanIndex,cameraName这三个字段,其中cameraNo是自增主键。 下面是批量插入的Dao层接口: void batchInsertCameras(@Param("li...

阿偶灬的专栏 1356
上一篇: 手搓MCP客户端动态调用多MCP服务,调用哪个你说了算!
下一篇: 开源新锐:SQL玩转搜索引擎?Manticore颠覆你的认知
智_永无止境
博客等级 码龄10年 803粉丝 406原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

智_永无止境

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值