随笔 - 836  文章 - 1 评论 - 40 阅读 - 102万
< 2025年3月 >
23 24 25 26 27 28 1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28 29
30 31 1 2 3 4 5

 参考原文:   https://segmentfault.com/a/1190000040457995?sort=newest

问题:  
使用Mongodb的事务:

pringframework.data.mongodb.UncategorizedMongoDbException: Command failed with error 112 (WriteConflict): 'WriteConflict'
WriteConflict error: this operation conflicted with another operation. Please retry your operation or multi-document transaction.,

 

原因

Mongodb的事务属于乐观事务,不同于MySql悲观事务
Mongodb的事务使用的隔离级别为SI(Snapshot Isolation,篇外连接)

1、乐观事务会有冲突检测,检测到冲突则立即throw Conflict(也就是上面提到的WriteConflict)
2、乐观事务推崇的是更少的资源锁定时间,达到更高的效率,跟以往我们使用的MySql事务还是有比较大的区别的
3、所以可以理解不支持MySql那样的行锁-悲观锁

对于出现冲突后的处理方案

MongoDb官方推荐在driver层重试,也就是出现WriteConflict异常自动进行重试。

 

 

 

解决方案

经过查阅资料

spring-data-mongo官方推荐使用spring-retry框架进行重试

添加依赖

        <dependency>
            <groupId>org.springframework.retry</groupId>
            <artifactId>spring-retry</artifactId>
        </dependency>

启用spring-Retry

Application类上面需要添加@EnableRetry注解

方式一 简单使用:  

propagation = Propagation.REQUIRED  也可以
    @Retryable(value = UncategorizedMongoDbException.class, exceptionExpression = "#{message.contains('WriteConflict error')}", maxAttempts = 128, backoff = @Backoff(delay = 500))
    @Transactional(propagation = Propagation.REQUIRES_NEW, rollbackFor = Exception.class, timeout = 120)
    public void updateScoreRemark(String remark) {
        业务代码
    }

 

方式二 快捷使用

定义@MongoTransactional注解

复制代码
import org.springframework.data.mongodb.UncategorizedMongoDbException;
import org.springframework.retry.annotation.Backoff;
import org.springframework.retry.annotation.Retryable;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import java.lang.annotation.*;

/**
 * Mongo事务注解
 */
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
@Retryable(value = UncategorizedMongoDbException.class, exceptionExpression = "#{message.contains('WriteConflict error')}", maxAttempts = 128, backoff = @Backoff(delay = 500))
@Transactional(propagation = Propagation.REQUIRES_NEW, rollbackFor = Exception.class, timeout = 120)
public @interface MongoTransactional {

}
复制代码

 

使用@MongoTransactional注解

@MongoTransactional
    public void updateScoreRemark(String remark) {
        业务代码
    }

注意事项

@Retryable

maxAttempts = 128 参数是最大重试次数,可自行调整
backoff = @Backoff(delay = 500) 重试间隔时间(毫秒) 可自行调整

@Transactional

propagation = Propagation.REQUIRES_NEW 是每次创建新的事务(传播级别)   

propagation = Propagation.REQUIRED  也可以
由于mongodb出现WriteConflict 的时候会abort(rollback)事务,所以重试的时候需要生成新的事务
所以推荐在业务入口处增加【重试和事务】的注解,其他地方不要加,避免一个业务方法中出现两个事务。

 





posted on   lshan  阅读(116)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· C#/.NET/.NET Core优秀项目和框架2025年2月简报
· 什么是nginx的强缓存和协商缓存
· 一文读懂知识蒸馏
· Manus爆火,是硬核还是营销?
历史上的今天:
2022-07-29 分布式事务( XA) -- seata eurake springboot mysql (1.4.2)
2021-07-29 easyexcel 自动设置列宽(转)
点击右上角即可分享
微信分享提示