结论:
设置事务隔离级别和传播方式不对,导致不同线程从数据库获取的序列号相同,导致序列号重复。
解决办法,调整事务隔离级别以及传播级别。
代码环境:
springboot:2.7.6
java:jdk8
mysql:8.0
伪代码:
public class BusinessProcess{
@Resource
private AccountRuleService accountRuleService;
@Transaction()
public void openAccount(User user){
check(user);
String account = accountRuleService.generate(user.getBusinessType());
user.setAccount(account);
saveUser(user);
}
}
public class AccountRuleService {
@Resource
private RedissionClient redissionClient;
@Resource
private AccountRuleMapper accountRuleMapper;
private final String LOCK_KEY = "lock_key";
public String generate(String businessType){
RLock lock = redissionClient.getLock(LOCK_KEY)
try{
String serialNo = accountRuleMapper.selectSerialNo(businessType);
serialNo = incrementSerialNo(serialNo);
accountRuleMapper.update(businessType,serialNo);
return serialNo;
}
finally{
lock.unlock(LOCK_KEY);
}
}
}
业务流程:
后端接收http请求,调用BusinessProcess,由AccountRuleServic生成序号。最后回写数据库。
存在问题:
1. redissionClient 只创建了lock但是没有lock()
这会导致多个线程同时获取数据库表序列号,拿到相同的序列号
2. AccountRuleService#generate()方法没有单独设置事务
这里没有单独设置事务会存在一个严重的问题
当线程1生成序号后,但未还没提交事务时。线程2读取序号时,还是获取旧的序号。发生读取重复序号。
解决办法
1. RedissionClient获取lock后要加锁
2. 通过调整generate()方法的事务隔离级别以及传播方式
propagation = Propagation.REQUIRES_NEW, isolation = Isolation.READ_UNCOMMITTED
重点在于READ_UNCOMMITTED
保证了当线程1执行update语句后能被其他线程观测到