SpringBoot JPA repository调用saveAll时抛出MongoDB写入冲突如何解决
MongoDB 事务写冲突(错误码112)解决方案
错误根因
你遇到的MongoDB错误码112属于多文档事务瞬时写冲突,错误标签TransientTransactionError明确表示这类错误可通过重试解决。现有代码的核心问题:
synchronized锁的释放时机早于@Transactional的事务提交时机:@Transactional是通过AOP在方法执行完成后才提交事务,你加在方法上的synchronized在方法执行结束就释放锁,此时事务还未提交,其他线程即可进入操作同批数据,直接触发MongoDB的事务冲突检测。
解决方案
1. 配置瞬时事务异常重试机制
这是官方推荐的处理TransientTransactionError类错误的最优方案,步骤如下:
- 引入Spring Retry依赖(Maven示例):
<dependency> <groupId>org.springframework.retry</groupId> <artifactId>spring-retry</artifactId> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-aspects</artifactId> </dependency>
- 启动类添加
@EnableRetry注解开启重试功能 - 调整业务方法,添加重试配置:
@Retryable( retryFor = UncategorizedMongoDbException.class, maxAttempts = 3, // 最大重试3次 backoff = @Backoff(delay = 100, multiplier = 2) // 初始间隔100ms,指数退避 ) @Transactional public void myMethod(List<MyEntity> myEntities) { saveEntities(myEntities); } // 重试全部失败后的兜底处理逻辑 @Recover public void myMethodRecover(UncategorizedMongoDbException e, List<MyEntity> myEntities) { // 可做日志记录、告警、落盘后续补发等逻辑 }
2. 调整锁的作用范围(单体应用适用)
如果是单实例部署,要让锁覆盖事务的完整生命周期,把锁加在事务方法的外层调用处,示例逻辑:
// 外层调用类 public void outerInvoke(List<MyEntity> myEntities) { synchronized(this) { myService.myMethod(myEntities); // 调用加了@Transactional的方法 } }
此时锁释放时事务已经提交完成,不会出现锁提前释放的问题。
3. 优化事务粒度
- 非必要场景去掉
@Transactional注解:单集合批量写入本身就具备原子性,没有跨集合/跨文档的事务强一致要求的话,不需要开启多文档事务,从根源避免写冲突。 - 缩小事务执行时长:不要在事务中执行RPC调用、文件IO等耗时操作,事务持有时间越长,冲突概率越高。
4. 分布式场景添加分布式锁
如果是多实例部署,synchronized属于进程级锁,完全不生效,需要引入Redis分布式锁、MongoDB自旋锁等分布式锁,锁的生命周期覆盖整个事务提交流程。
5. 拆分大批次写入
如果单次保存的实体列表超过50条,拆分为小批量(20-50条/批次)分开写入,降低单次事务锁定的文档数量,减少冲突概率。
内容的提问来源于stack exchange,提问作者Techy
相关产品推荐
相关产品推荐

