news 2026/6/10 18:25:58

MyBatis批量插入从5分钟优化到3秒,我做了这3件事

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
MyBatis批量插入从5分钟优化到3秒,我做了这3件事

上周接了个数据迁移的活,要把10万条数据从老系统导入新系统。

写了个简单的批量插入,跑起来一看——5分钟

领导说太慢了,能不能快点?

折腾了一下午,最后优化到3秒,记录一下过程。


最初的代码(5分钟)

最开始写的很简单,foreach循环插入:

// 方式1:循环单条插入(最慢)for(Useruser:userList){userMapper.insert(user);}

10万条数据,每条都要走一次网络请求、一次SQL解析、一次事务提交。

算一下:假设每条插入需要3ms,10万条就是300秒 = 5分钟。

这是最蠢的写法,但我见过很多项目都这么写。


第一次优化:批量SQL(30秒)

把循环插入改成批量SQL:

<!-- Mapper.xml --><insertid="batchInsert">INSERT INTO user (name, age, email) VALUES<foreachcollection="list"item="item"separator=",">(#{item.name}, #{item.age}, #{item.email})</foreach></insert>
// 分批插入,每批1000条intbatchSize=1000;for(inti=0;i<userList.size();i+=batchSize){intend=Math.min(i+batchSize,userList.size());List<User>batch=userList.subList(i,end);userMapper.batchInsert(batch);}

从5分钟降到30秒,提升10倍。

原理:一条SQL插入多条数据,减少网络往返次数。

但还有问题:30秒还是太慢。


第二次优化:JDBC批处理(8秒)

MySQL有个参数叫rewriteBatchedStatements,开启后可以把多条INSERT合并成一条。

第一步:修改数据库连接URL

jdbc:mysql://localhost:3306/test?rewriteBatchedStatements=true

第二步:使用MyBatis的批处理模式

@AutowiredprivateSqlSessionFactorysqlSessionFactory;publicvoidbatchInsertWithExecutor(List<User>userList){try(SqlSessionsqlSession=sqlSessionFactory.openSession(ExecutorType.BATCH)){UserMappermapper=sqlSession.getMapper(UserMapper.class);intbatchSize=1000;for(inti=0;i<userList.size();i++){mapper.insert(userList.get(i));if((i+1)%batchSize==0){sqlSession.flushStatements();sqlSession.clearCache();}}sqlSession.flushStatements();sqlSession.commit();}}

从30秒降到8秒

原理ExecutorType.BATCH模式下,MyBatis会缓存SQL,最后一次性发送给数据库执行。配合rewriteBatchedStatements=true,MySQL驱动会把多条INSERT合并。


第三次优化:多线程并行(3秒)

8秒还是不够快,上多线程:

publicvoidparallelBatchInsert(List<User>userList){intthreadCount=4;// 根据数据库连接池大小调整intbatchSize=userList.size()/threadCount;ExecutorServiceexecutor=Executors.newFixedThreadPool(threadCount);List<Future<?>>futures=newArrayList<>();for(inti=0;i<threadCount;i++){intstart=i*batchSize;intend=(i==threadCount-1)?userList.size():(i+1)*batchSize;List<User>subList=userList.subList(start,end);futures.add(executor.submit(()->{batchInsertWithExecutor(subList);}));}// 等待所有任务完成for(Future<?>future:futures){try{future.get();}catch(Exceptione){thrownewRuntimeException(e);}}executor.shutdown();}

从8秒降到3秒

注意事项

  1. 线程数不要超过数据库连接池大小
  2. 如果需要事务一致性,这个方案不适用
  3. 要考虑主键冲突的问题

优化效果对比

方案耗时提升倍数
循环单条插入300秒基准
批量SQL30秒10倍
JDBC批处理8秒37倍
多线程并行3秒100倍

踩过的坑

坑1:foreach拼接SQL过长

<foreachcollection="list"item="item"separator=",">

如果一次插入太多条,SQL会非常长,可能超过max_allowed_packet限制。

解决:分批插入,每批500-1000条。

坑2:rewriteBatchedStatements不生效

检查几个点:

  1. URL参数是否正确:rewriteBatchedStatements=true
  2. 是否使用了ExecutorType.BATCH
  3. MySQL驱动版本是否太旧

坑3:自增主键返回问题

批量插入时想获取自增主键:

<insertid="batchInsert"useGeneratedKeys="true"keyProperty="id">

注意rewriteBatchedStatements=true时,自增主键返回可能有问题,需要升级MySQL驱动到8.0.17+。

坑4:内存溢出

10万条数据一次性加载到内存,可能OOM。

解决:分页读取 + 分批插入。

intpageSize=10000;inttotal=countTotal();for(inti=0;i<total;i+=pageSize){List<User>page=selectByPage(i,pageSize);batchInsertWithExecutor(page);}

最终方案代码

@ServicepublicclassBatchInsertService{@AutowiredprivateSqlSessionFactorysqlSessionFactory;/** * 高性能批量插入 * 10万条数据约3秒 */publicvoidhighPerformanceBatchInsert(List<User>userList){if(userList==null||userList.isEmpty()){return;}intthreadCount=Math.min(4,Runtime.getRuntime().availableProcessors());intbatchSize=(int)Math.ceil((double)userList.size()/threadCount);ExecutorServiceexecutor=Executors.newFixedThreadPool(threadCount);CountDownLatchlatch=newCountDownLatch(threadCount);for(inti=0;i<threadCount;i++){intstart=i*batchSize;intend=Math.min((i+1)*batchSize,userList.size());if(start>=userList.size()){latch.countDown();continue;}List<User>subList=newArrayList<>(userList.subList(start,end));executor.submit(()->{try{doBatchInsert(subList);}finally{latch.countDown();}});}try{latch.await();}catch(InterruptedExceptione){Thread.currentThread().interrupt();}executor.shutdown();}privatevoiddoBatchInsert(List<User>userList){try(SqlSessionsqlSession=sqlSessionFactory.openSession(ExecutorType.BATCH,false)){UserMappermapper=sqlSession.getMapper(UserMapper.class);for(inti=0;i<userList.size();i++){mapper.insert(userList.get(i));if((i+1)%1000==0){sqlSession.flushStatements();sqlSession.clearCache();}}sqlSession.flushStatements();sqlSession.commit();}}}

总结

优化点关键配置
批量SQLforeach拼接,分批1000条
JDBC批处理rewriteBatchedStatements=true+ExecutorType.BATCH
多线程线程数 ≤ 连接池大小

核心原则:减少网络往返 + 减少事务次数 + 并行处理。


有其他批量操作的优化经验,欢迎评论区交流~

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/6/10 10:51:40

【2025最新高维多目标优化】基于城市场景下无人机三维路径规划的导航变量的多目标粒子群优化算法NMOPSO研究附Matlab代码

✅作者简介&#xff1a;热爱科研的Matlab仿真开发者&#xff0c;擅长数据处理、建模仿真、程序设计、完整代码获取、论文复现及科研仿真。&#x1f34e; 往期回顾关注个人主页&#xff1a;Matlab科研工作室&#x1f34a;个人信条&#xff1a;格物致知,完整Matlab代码及仿真咨询…

作者头像 李华
网站建设 2026/6/10 10:54:33

26、Linux 文本格式化与打印技术全解析

Linux 文本格式化与打印技术全解析 1. printf 的文本格式化应用 printf 主要用于脚本中对表格数据进行格式化,而非直接在命令行使用。不过,它也能解决多种格式化问题。 - 输出以制表符分隔的字段 : [me@linuxbox ~]$ printf "%s\t%s\t%s\n" str1 str2 str3 …

作者头像 李华
网站建设 2026/6/10 18:21:27

27、Linux 打印与程序编译指南

Linux 打印与程序编译指南 1. 向打印机发送打印任务 在类 Unix 系统中,CUPS 打印套件支持两种传统的打印方法。一种是 Berkeley 或 LPD 方法(用于 Unix 的 Berkeley 软件发行版),使用 lpr 程序;另一种是 SysV 方法(来自 Unix 的 System V 版本),使用 lp 程序。这…

作者头像 李华
网站建设 2026/6/9 19:48:17

实时特征窗口僵化 房颤检测滞后 动态调整才稳住预警

&#x1f4dd; 博客主页&#xff1a;jaxzheng的CSDN主页 目录 医疗数据科学&#xff1a;当Excel表格遇上听诊器 一、救命&#xff01;我的电子病历会自己长腿跑&#xff1f; 二、AI医生&#xff1a;你吃的是药&#xff0c;我看的是数据流 三、隐私保护&#xff1a;我的体检报告…

作者头像 李华
网站建设 2026/6/10 1:08:43

边缘Agent部署黄金标准出炉:行业头部企业都在用的8步法

第一章&#xff1a;边缘Agent部署的行业背景与演进随着物联网&#xff08;IoT&#xff09;、5G通信和人工智能技术的快速发展&#xff0c;数据正以前所未有的速度在终端设备端产生。传统的集中式云计算架构在处理海量实时数据时面临延迟高、带宽压力大和隐私泄露等挑战。在此背…

作者头像 李华