产生问题:
经过查询资料,发现上面的问题是由一个参数引起的maxTransactionLockRequestTimeoutMillis
这参数是事务锁最大等待事件(毫秒)
官网链接(maxTransactionLockRequestTimeoutMillis) 这个参数默认值为5(毫秒),也就意味着获取锁超出了这个时间就会提示WriteConflict
解决方案:
方式一:使用这个可以在线修改这个值
db.adminCommand( { setParameter: 1, maxTransactionLockRequestTimeoutMillis: 3000 } );
方式二:启动的时候加入参数
mongod --setParameter maxTransactionLockRequestTimeoutMillis=3000
方式三:在(/etc/mongod.cnf
)中加入一下配置
setParameter = maxTransactionLockRequestTimeoutMillis=3000
其中3000
为最大锁等待事件,可自由调配
情况2:来源(Springboot 解决mongodb transaction WriteConflict问题 - SegmentFault 思否)
Mongodb的事务属于乐观事务,不同于MySql悲观事务
Mongodb的事务使用的隔离级别为SI(Snapshot Isolation,篇外连接)
1、乐观事务会有冲突检测,检测到冲突则立即throw Conflict(也就是上面提到的WriteConflict)
2、乐观事务推崇的是更少的资源锁定时间,达到更高的效率,跟以往我们使用的MySql事务还是有比较大的区别的
3、所以可以理解不支持MySql那样的行锁-悲观锁
对于出现冲突后的处理方案
MongoDb官方推荐在driver层重试,也就是出现WriteConflict异常自动进行重试
解决方案:(以下仅spring-data框架解决方案,详细的请看[岁月安然]原文)
spring-data-mongo官方推荐使用spring-retry框架进行重试
添加依赖
<dependency> <groupId>org.springframework.retry</groupId> <artifactId>spring-retry</artifactId> </dependency>
启用spring-Retry
Application类上面需要添加@EnableRetry注解
方式一 简单使用
@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 是每次创建新的事务(传播级别)
由于mongodb出现WriteConflict 的时候会abort(rollback)事务,所以重试的时候需要生成新的事务
所以推荐在业务入口处增加【重试和事务】的注解,其他地方不要加,避免一个业务方法中出现两个事务。
timeout = 120 是事务超时时间(秒) 虽然目前没测试出来有什么用,还是建议设置一下