MyBatis-Plus saveBatch异步事务问题分析与解决方案
1. 问题背景与现象描述最近在开发一个电商订单处理系统时遇到了一个棘手的问题使用MyBatis-Plus的saveBatch方法在异步线程中批量插入数据时发现事务没有正常提交。具体表现为系统使用Spring Boot MyBatis-Plus架构订单创建后需要异步处理库存扣减和日志记录使用Async注解标记的异步方法中调用了saveBatch批量插入操作日志日志表中有部分记录插入成功部分记录丢失没有抛出任何异常但数据不完整这个问题在测试环境偶发出现但在高并发压测时几乎必现。经过排查发现这与MyBatis-Plus的批量操作机制、Spring事务管理以及异步线程处理有密切关系。2. MyBatis-Plus saveBatch原理剖析2.1 saveBatch的默认实现MyBatis-Plus的saveBatch方法默认实现是这样的// MyBatis-Plus 3.x版本的默认实现 Transactional(rollbackFor Exception.class) Override public boolean saveBatch(CollectionT entityList, int batchSize) { String sqlStatement sqlStatement(SqlMethod.INSERT_ONE); return executeBatch(entityList, batchSize, (sqlSession, entity) - { sqlSession.insert(sqlStatement, entity); }); }关键点方法本身带有Transactional注解使用MyBatis的SqlSession执行批量插入默认batchSize为10002.2 批量操作的执行流程saveBatch的实际执行流程可以分为以下几个步骤开启事务由Spring管理对集合进行分片处理根据batchSize对每个分片执行批量插入提交事务如果成功或回滚如果失败问题在于当这个方法在异步线程中执行时第4步的事务提交可能不会按预期工作。3. 异步环境中的事务问题3.1 Spring事务管理机制Spring的事务管理是基于ThreadLocal实现的关键点包括事务上下文存储在ThreadLocal中Transactional注解的事务传播行为默认是REQUIRED异步方法会使用新的线程执行无法继承原有的事务上下文3.2 Async与事务的交互当我们在异步方法中使用Transactional时会遇到以下问题异步方法本身需要Async注解如果异步方法内部有Transactional会创建新的事务这两个注解的执行顺序和交互需要特别注意3.3 典型的问题场景在我们的案例中代码结构大致如下Service public class OrderService { Autowired private AsyncLogService asyncLogService; Transactional public void createOrder(OrderDTO dto) { // 订单创建逻辑... asyncLogService.saveOperationLog(logs); } } Service public class AsyncLogService { Async Transactional(propagation Propagation.REQUIRES_NEW) public void saveOperationLog(ListOperationLog logs) { logMapper.saveBatch(logs); // 使用MyBatis-Plus的saveBatch } }这种情况下虽然两个方法都有Transactional注解但由于异步执行事务可能无法正常提交。4. 问题排查过程4.1 复现问题为了准确复现问题我们设计了以下测试方案准备1000条测试数据在异步方法中调用saveBatch观察数据库中的记录数量检查日志是否有异常测试结果有时插入全部成功有时部分成功如插入300条没有异常日志4.2 日志分析通过增加事务相关的日志配置logging.level.org.springframework.transactionDEBUG logging.level.org.mybatisTRACE从日志中可以观察到主线程的事务正常开启和提交异步线程中的事务有时没有提交日志没有回滚日志4.3 线程池配置检查发现项目中配置了自定义的线程池Configuration EnableAsync public class AsyncConfig implements AsyncConfigurer { Override public Executor getAsyncExecutor() { ThreadPoolTaskExecutor executor new ThreadPoolTaskExecutor(); executor.setCorePoolSize(5); executor.setMaxPoolSize(10); executor.setQueueCapacity(100); executor.setThreadNamePrefix(async-); executor.initialize(); return executor; } }线程池配置可能导致任务被拒绝或线程被回收影响事务提交。5. 解决方案5.1 方案一使用编程式事务管理改造异步方法使用TransactionTemplateService public class AsyncLogService { Autowired private TransactionTemplate transactionTemplate; Async public void saveOperationLog(ListOperationLog logs) { transactionTemplate.execute(status - { try { logMapper.saveBatch(logs); return Boolean.TRUE; } catch (Exception e) { status.setRollbackOnly(); throw e; } }); } }优点明确控制事务边界避免注解方式的问题缺点代码稍显冗长5.2 方案二调整事务传播行为修改Transactional的传播行为Async Transactional(propagation Propagation.NESTED) public void saveOperationLog(ListOperationLog logs) { logMapper.saveBatch(logs); }注意NESTED需要数据库支持保存点不是所有场景都适用5.3 方案三使用同步批量插入如果不必须异步可以改为同步执行Service public class OrderService { Transactional public void createOrder(OrderDTO dto) { // 订单创建逻辑... logMapper.saveBatch(logs); // 同步执行 } }最简单可靠但可能影响性能。5.4 最终采用的方案我们最终选择了方案一结合以下优化增加事务超时设置添加重试机制完善日志记录完整实现Async public void saveOperationLog(ListOperationLog logs) { transactionTemplate.setTimeout(30); // 30秒超时 transactionTemplate.execute(status - { try { int retryCount 0; while (retryCount 3) { try { logMapper.saveBatch(logs); return Boolean.TRUE; } catch (Exception e) { retryCount; if (retryCount 3) { throw e; } Thread.sleep(1000 * retryCount); } } return Boolean.FALSE; } catch (Exception e) { log.error(保存操作日志失败, e); status.setRollbackOnly(); throw new RuntimeException(保存操作日志失败, e); } }); }6. 深入原理为什么会出现这个问题6.1 MyBatis-Plus批量操作的本质虽然叫批量插入但默认实现其实是循环单条插入不是真正的JDBC批量addBatch/executeBatch每条insert都是独立的SQL语句依赖事务保证原子性6.2 Spring异步执行的原理Async的工作机制通过AOP代理拦截方法调用提交到线程池执行原始线程继续执行新线程中方法执行6.3 事务失效的根本原因综合来看问题根源在于异步线程可能被突然终止如线程池回收事务提交发生在异步线程中Spring无法保证异步线程的事务一定会提交MyBatis-Plus的批量不是原子操作7. 性能优化建议7.1 使用真正的批量插入可以重写saveBatch方法使用JDBC的批量操作public class CustomServiceImplM extends BaseMapperT, T extends ServiceImplM, T { Override Transactional public boolean saveBatch(CollectionT entityList, int batchSize) { try (SqlSession batchSqlSession sqlSessionBatch()) { int i 0; for (T entity : entityList) { batchSqlSession.insert(sqlStatement(SqlMethod.INSERT_ONE), entity); if (i 1 i % batchSize 0) { batchSqlSession.flushStatements(); } i; } batchSqlSession.flushStatements(); return true; } } }7.2 调整批量大小根据数据库性能调整batchSizeMySQL建议500-1000Oracle建议100-200SQL Server建议1000-20007.3 使用多线程批量插入对于大数据量可以结合多线程public void batchInsertConcurrent(ListData dataList) { int threadCount 4; int batchSize dataList.size() / threadCount; ExecutorService executor Executors.newFixedThreadPool(threadCount); ListFuture? futures new ArrayList(); for (int i 0; i threadCount; i) { int from i * batchSize; int to (i threadCount - 1) ? dataList.size() : (i 1) * batchSize; ListData subList dataList.subList(from, to); futures.add(executor.submit(() - { transactionTemplate.execute(status - { customService.saveBatch(subList); return null; }); })); } for (Future? future : futures) { try { future.get(); } catch (Exception e) { // 处理异常 } } executor.shutdown(); }8. 其他注意事项8.1 事务隔离级别的影响在高并发下还需要考虑隔离级别READ_COMMITTED可能导致幻读SERIALIZABLE性能影响大建议根据业务需求选择合适的隔离级别8.2 连接池配置确保连接池配置合理足够大的最大连接数合理的超时设置适当的验证查询例如HikariCP配置spring.datasource.hikari.maximum-pool-size20 spring.datasource.hikari.connection-timeout30000 spring.datasource.hikari.validation-timeout5000 spring.datasource.hikari.leak-detection-threshold600008.3 监控与告警建议添加以下监控事务执行时间监控批量操作成功率监控线程池使用情况监控可以使用Micrometer Prometheus Grafana实现。9. 常见问题解答9.1 为什么部分数据插入成功了这是因为MyBatis-Plus的saveBatch默认不是原子操作。在事务提交前部分插入已经执行但如果事务最终没有提交这些已执行的插入可能会被保留取决于数据库的具体实现。9.2 如何确定是事务问题可以通过以下方法验证在方法结束后手动抛出异常看是否回滚检查数据库事务日志使用Spring的TransactionSynchronizationManager.isActualTransactionActive()9.3 除了saveBatch还有其他方法吗可以考虑使用MyBatis的 标签实现批量插入使用JDBC的addBatch/executeBatch使用存储过程处理批量数据9.4 异步事务的最佳实践是什么建议避免在异步方法中进行复杂的多步骤事务如果必须使用事务确保有完善的错误处理和重试机制考虑使用消息队列实现最终一致性监控异步任务的执行情况10. 总结与个人建议经过这次问题排查我总结了以下几点经验不要想当然地认为批量操作就是原子的要了解框架的具体实现异步和事务结合使用时需要格外小心生产环境中的事务问题往往在高压下才会暴露完善的日志和监控是快速定位问题的关键在实际项目中我建议对于关键业务操作优先考虑同步执行如果必须异步考虑使用消息队列等更可靠的机制对批量操作进行充分的压力测试编写详细的文档记录这些坑避免团队成员重复踩坑最后记住一个原则分布式系统没有完美的事务解决方案我们需要根据业务特点在一致性和性能之间找到平衡点。

相关新闻

Pinpoint全链路监控:从分布式追踪原理到生产环境部署实战

Pinpoint全链路监控:从分布式追踪原理到生产环境部署实战

1. 从单体到微服务:为什么我们需要全链路监控?如果你是从单体应用时代一路走过来的开发者,大概还记得那种“岁月静好”的日子。一个应用,一个数据库,出了问题,登录服务器,看日志,查数…

2026/8/3 2:42:03 阅读更多 →
MPC Video Renderer终极指南:RTX HDR加持的专业级视频渲染器

MPC Video Renderer终极指南:RTX HDR加持的专业级视频渲染器

MPC Video Renderer终极指南:RTX HDR加持的专业级视频渲染器 【免费下载链接】VideoRenderer RTX HDR modded into MPC-VideoRenderer. 项目地址: https://gitcode.com/gh_mirrors/vid/VideoRenderer 想要在Windows系统上获得影院级的视频播放体验吗&#xf…

2026/8/3 2:42:03 阅读更多 →
Android UID详解:应用身份标识、沙盒隔离与共享机制

Android UID详解:应用身份标识、沙盒隔离与共享机制

1. 项目概述:理解Android世界的“身份证”系统在Android开发或者系统维护的日常里,你是否遇到过这样的场景:调试一个应用时,发现它无法访问另一个应用创建的某个文件;或者,在分析系统日志时,看到…

2026/8/3 2:42:03 阅读更多 →

最新新闻

Matomo埋点工具实战:从数据采集到分析应用

Matomo埋点工具实战:从数据采集到分析应用

1. Matomo埋点工具全景解析:从数据采集到分析应用作为一款开源的网站分析平台,Matomo在数据隐私保护日益重要的今天正获得越来越多企业的青睐。不同于传统GA工具,Matomo允许用户完全掌控自己的数据资产。在实际项目中,我们团队已经…

2026/8/3 3:23:20 阅读更多 →
商品管理模块:分类、上下架、图片、规格管理

商品管理模块:分类、上下架、图片、规格管理

商品管理模块:分类、上下架、图片、规格管理商品管理是无人售货柜的"粮草官",没有商品,后面的订单、支付、库存全部是空谈。今天我们就来把商品管理这个基础模块拆开揉碎,一文讲透。一、商品在无人售货柜中的角色 在无人…

2026/8/3 3:23:20 阅读更多 →
javascript之string正则式

javascript之string正则式

1.search方法search() 返回第一个匹配的索引,未找到返回-1const str "Hello World"; const index str.search(/World/); console.log(index); // 62 match方法match() 用于在字符串中执行正则匹配,返回匹配结果的数组或nullconst str "…

2026/8/3 3:23:20 阅读更多 →
随机森林原理与实践:从决策树到集成学习的机器学习实战指南

随机森林原理与实践:从决策树到集成学习的机器学习实战指南

1. 从“一棵树”到“一片森林”:为什么我们需要随机森林?如果你接触过机器学习,大概率听说过决策树。它就像一个流程图,通过一系列“是/否”问题(比如“年龄是否大于30岁?”、“收入是否高于5万&#xff1f…

2026/8/3 3:23:20 阅读更多 →
做自媒体半年,我总结了5个让视频体积减少80%的技巧

做自媒体半年,我总结了5个让视频体积减少80%的技巧

很多新人做短视频,都会遇到一个问题:视频拍完很好看,但导出来却有2GB。其实,大部分视频都可以在保证观看体验的情况下,减少不少体积。第一,不要一味追求最高码率很多剪辑软件默认导出"最高画质"。…

2026/8/3 3:23:20 阅读更多 →
Android毕业设计开题报告撰写指南与技术要点

Android毕业设计开题报告撰写指南与技术要点

1. 为什么你的开题报告总被导师打回? 每年毕业季,总有一批学生在开题报告环节反复修改却始终无法通过。我指导过上百名Android方向的学生,发现90%的失败案例都源于同一个问题——过度依赖网络模板。那些从百度文库下载的"万能模板"…

2026/8/3 3:22:20 阅读更多 →

日新闻

3个让你工作效率翻倍的Umi-OCR实战技巧:免费离线文字识别完全指南

3个让你工作效率翻倍的Umi-OCR实战技巧:免费离线文字识别完全指南

3个让你工作效率翻倍的Umi-OCR实战技巧:免费离线文字识别完全指南 【免费下载链接】Umi-OCR OCR software, free and offline. 开源、免费的离线OCR软件。支持截屏/批量导入图片,PDF文档识别,排除水印/页眉页脚,扫描/生成二维码。…

2026/8/3 0:00:47 阅读更多 →
[具身智能-181]:PC+服务器+具身机器人:构建具身智能从仿真到量产的闭环迭代混合架构

[具身智能-181]:PC+服务器+具身机器人:构建具身智能从仿真到量产的闭环迭代混合架构

PC服务器具身机器人:构建具身智能从仿真到量产的闭环迭代混合架构一、前言:具身智能需要“混合算力闭环系统”传统人工智能依赖云端静态数据集训练,不具备物理交互能力,无法适应真实世界的不确定性。具身智能(Embodied…

2026/8/3 0:00:47 阅读更多 →
[具身智能-181]:大分布式通信模型对比:看懂为什么 DDS 是 ROS2 底层通信最优解

[具身智能-181]:大分布式通信模型对比:看懂为什么 DDS 是 ROS2 底层通信最优解

前言构建机器人、具身智能这类分布式实时系统,通信底座直接决定整套系统的实时性、容错性、组网能力。分布式领域长期存在 4 类经典通信架构:点对点模式、Broker 中间代理模式、广播模式、以数据为中心(DDS)模式。很多开发者疑惑&…

2026/8/3 0:00:47 阅读更多 →

周新闻

最大流算法详解:从水管网络到Ford-Fulkerson与Dinic实战

最大流算法详解:从水管网络到Ford-Fulkerson与Dinic实战

1. 从水管网络到最大流:一个核心问题的诞生想象一下,你是一个城市供水系统的总工程师。你的城市有多个水源(水库),需要通过一个复杂的地下管道网络,将水输送到各个居民区。每条管道都有其最大通水能力&…

2026/8/2 0:00:38 阅读更多 →
基于Springboot的企业门户网站(源码+LW+调试文档+讲解)

基于Springboot的企业门户网站(源码+LW+调试文档+讲解)

温馨提示:本人主页置顶文章(点我)开头有 CSDN 平台官方提供的学长联系方式的名片! 温馨提示:本人主页置顶文章(点我)开头有 CSDN 平台官方提供的学长联系方式的名片! 温馨提示:本人主页置顶文章(点我)开头有 CSDN 平台…

2026/8/3 1:53:31 阅读更多 →
MATLAB xcorr函数详解:从互相关原理到四大实战应用

MATLAB xcorr函数详解:从互相关原理到四大实战应用

1. 从一次信号“找茬”说起:为什么我们需要互相关几年前,我在处理一组声学传感器数据时遇到了一个棘手的问题。我有两个麦克风记录了一段相同的音频信号,理论上它们接收到的声音波形应该非常相似,只是由于麦克风位置不同&#xff…

2026/8/2 0:00:38 阅读更多 →

月新闻

免费解锁百度网盘SVIP加速:macOS用户必备的下载提速终极指南

免费解锁百度网盘SVIP加速:macOS用户必备的下载提速终极指南

免费解锁百度网盘SVIP加速:macOS用户必备的下载提速终极指南 【免费下载链接】BaiduNetdiskPlugin-macOS For macOS.百度网盘 破解SVIP、下载速度限制~ 项目地址: https://gitcode.com/gh_mirrors/ba/BaiduNetdiskPlugin-macOS 还在为百度网盘macOS版的龟速下…

2026/8/2 6:34:16 阅读更多 →
终极ncmdump指南:3分钟实现网易云NCM音乐解密与格式转换

终极ncmdump指南:3分钟实现网易云NCM音乐解密与格式转换

终极ncmdump指南:3分钟实现网易云NCM音乐解密与格式转换 【免费下载链接】ncmdump 项目地址: https://gitcode.com/gh_mirrors/ncmd/ncmdump 还在为网易云音乐下载的NCM格式文件无法在其他播放器播放而烦恼吗?ncmdump解密工具帮你轻松解决这个困…

2026/8/2 2:47:48 阅读更多 →
HarmonyOS 应用开发《掌上英语》第81篇: 智能体卡片:为英语学习 App 打造桌面级学习助手

HarmonyOS 应用开发《掌上英语》第81篇: 智能体卡片:为英语学习 App 打造桌面级学习助手

AgentCard 智能体卡片:为英语学习 App 打造桌面级学习助手适用平台:HarmonyOS 7.0 (API 26 Beta)一、引言 HarmonyOS 7.0(API 26 Beta)新增了 AgentCard 智能体卡片能力,这是继 HMAF(鸿蒙智能体框架&#x…

2026/8/2 0:23:22 阅读更多 →