news 2026/2/28 22:26:01

如何让MyBatis批量插入从5分钟缩短到3秒?我的三个关键优化

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
如何让MyBatis批量插入从5分钟缩短到3秒?我的三个关键优化

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

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

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

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

最初的代码(5分钟)

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

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

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

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

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

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

把循环插入改成批量SQL:

<!-- Mapper.xml --> <insert id="batchInsert"> INSERT INTO user (name, age, email) VALUES <foreach collection="list" item="item" separator=","> (#{item.name}, #{item.age}, #{item.email}) </foreach> </insert>
// 分批插入,每批1000条 int batchSize = 1000; for (int i = 0; i < userList.size(); i += batchSize) { int end = 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的批处理模式
@Autowired private SqlSessionFactory sqlSessionFactory; public void batchInsertWithExecutor(List<User> userList) { try (SqlSession sqlSession = sqlSessionFactory.openSession(ExecutorType.BATCH)) { UserMapper mapper = sqlSession.getMapper(UserMapper.class); int batchSize = 1000; for (int i = 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秒还是不够快,上多线程:

public void parallelBatchInsert(List<User> userList) { int threadCount = 4; // 根据数据库连接池大小调整 int batchSize = userList.size() / threadCount; ExecutorService executor = Executors.newFixedThreadPool(threadCount); List<Future<?>> futures = new ArrayList<>(); for (int i = 0; i < threadCount; i++) { int start = i * batchSize; int end = (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 (Exception e) { thrownew RuntimeException(e); } } executor.shutdown(); }

从8秒降到3秒。

注意事项:

  • 线程数不要超过数据库连接池大小

  • 如果需要事务一致性,这个方案不适用

  • 要考虑主键冲突的问题

优化效果对比

方案

耗时

提升倍数

循环单条插入

300秒

基准

批量SQL

30秒

10倍

JDBC批处理

8秒

37倍

多线程并行

3秒

100倍

踩过的坑

坑1:foreach拼接SQL过长
<foreach collection="list" item="item" separator=",">

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

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

坑2:rewriteBatchedStatements不生效

检查几个点:

  • URL参数是否正确:rewriteBatchedStatements=true

  • 是否使用了ExecutorType.BATCH

  • MySQL驱动版本是否太旧

坑3:自增主键返回问题

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

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

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

坑4:内存溢出

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

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

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

最终方案代码

@Service publicclass BatchInsertService { @Autowired private SqlSessionFactory sqlSessionFactory; /** * 高性能批量插入 * 10万条数据约3秒 */ public void highPerformanceBatchInsert(List<User> userList) { if (userList == null || userList.isEmpty()) { return; } int threadCount = Math.min(4, Runtime.getRuntime().availableProcessors()); int batchSize = (int) Math.ceil((double) userList.size() / threadCount); ExecutorService executor = Executors.newFixedThreadPool(threadCount); CountDownLatch latch = new CountDownLatch(threadCount); for (int i = 0; i < threadCount; i++) { int start = i * batchSize; int end = Math.min((i + 1) * batchSize, userList.size()); if (start >= userList.size()) { latch.countDown(); continue; } List<User> subList = new ArrayList<>(userList.subList(start, end)); executor.submit(() -> { try { doBatchInsert(subList); } finally { latch.countDown(); } }); } try { latch.await(); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } executor.shutdown(); } private void doBatchInsert(List<User> userList) { try (SqlSession sqlSession = sqlSessionFactory.openSession(ExecutorType.BATCH, false)) { UserMapper mapper = sqlSession.getMapper(UserMapper.class); for (int i = 0; i < userList.size(); i++) { mapper.insert(userList.get(i)); if ((i + 1) % 1000 == 0) { sqlSession.flushStatements(); sqlSession.clearCache(); } } sqlSession.flushStatements(); sqlSession.commit(); } } }

总结

优化点

关键配置

批量SQL

foreach拼接,分批1000条

JDBC批处理

rewriteBatchedStatements=true+ExecutorType.BATCH

多线程

线程数 ≤ 连接池大小

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

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

matlab代码:考虑实时市场联动的电力零售商鲁棒定价策略 考虑电力零售商日前定价、日前购电、...

matlab代码&#xff1a;考虑实时市场联动的电力零售商鲁棒定价策略 考虑电力零售商日前定价、日前购电、实时能量管理、电动汽车用户需求响应和电力市场统一出清价格等因素&#xff0c;建立了考虑电动汽车不确定性的电力零售商鲁棒定价模型。 然后&#xff0c;通过线性化方法将…

作者头像 李华
网站建设 2026/2/21 23:48:17

使用 MWGA 帮助 7 万行 Winforms 程序快速迁移到 WEB 前端

前言 MWGA&#xff0c;是 Make Winforms Great Again 的缩写&#xff0c;是一个帮助 WinForms 程序快速迁移到 Blazor WASM 平台的高效工具软件。近期&#xff0c;我们借助 MWGA 成功将一个约 7 万行 C# 代码的成熟商业 WinForms 程序迁移至 Web 前端&#xff0c;整个过程快速…

作者头像 李华
网站建设 2026/2/27 13:58:36

Google打击IPIDEA住宅代理:代理 IP 池设施应具备哪些合规特征?

在 2026 年 1月29日&#xff0c;Google 威胁情报组&#xff08;GTIG&#xff09;宣布成功扰乱了一个被认为是全球最大的住宅代理网络之一 —— IPIDEA住宅代理基础设施&#xff0c;并依托法律行动与技术协作大规模关闭了其控制域名和代理流量通道。此举削弱了该网络对数百万设备…

作者头像 李华
网站建设 2026/2/22 11:12:08

军工项目中使用CKEDITOR粘贴Word公式会丢失格式吗?

前端老哥的CMS编辑器“文档全能王”&#xff1a;一键导入粘贴&#xff0c;680元开箱即用&#xff01; 兄弟们&#xff01;我是西安一名“头发没秃但项目没少接”的前端程序员&#xff0c;最近刚接了个CMS企业官网外包活——客户要在后台新闻编辑器里加“文档导入Word粘贴”功能…

作者头像 李华
网站建设 2026/2/27 6:43:52

2026年5款降AI工具横评:比话、嘎嘎、率零谁更值得用

论文被打回来那天&#xff0c;我花了三天时间测试了5款降AI工具。 从免费的到付费的&#xff0c;从几毛钱一千字的到8块钱一千字的&#xff0c;全试了一遍。结论很简单&#xff1a;追求效果选比话降AI&#xff0c;追求性价比选嘎嘎降AI&#xff0c;预算紧张选率零。 下面把测…

作者头像 李华
网站建设 2026/2/25 20:08:03

关于类 UNIX 系统的学习路线图

我曾经在知乎上看到有人问&#xff1a;为什么国产操作系统都是基于 Linux 内核来进行开发&#xff1f;为什么不能从零开始开发一套操作系统呢&#xff1f;事实上&#xff0c;这其中除了采用开源许可证能有效地规避一些法律问题&#xff0c;基于现成的代码能有助于降低开发成本之…

作者头像 李华