不懂什么是一锁二查三更新的,先看上面这个问题↑↑↑。
那么,用这个这个方式做幂等防控,就一定不会出现脏数据了么?先看下以下代码:
@Transactional(rollbackFor = Exception.class)
public boolean order(Request request) {
RLock lock = redisson.getLock(request.getIdentifier());
try {
//一锁
lock.lock();
//二查
OrderDo order = orderMapper.find(request.getIdentifier());
if (order!=null) {
resp.setResultFail(ReturnCodeEnum.USER_EXIST);
return false;
}
//三更新,保存订单数据
orderMapper.insertOrder(request);
} finally {
lock.unlock();
}
//三更新,保存订单流水
orderStreamMapper.insertOrder(request);
//三更新,保存事件流水
eventMapper.insert(request);
return true;
}
以上代码,其实是有可能出现脏数据的。
首先,我们可以看到,代码中通过@Transactional在order方法上增加了一个事务,事务的范围是整个方法,也就是方法全部执行完,事务才会提交。
也就是说,代码完第28行之后,事务才会提交。可是锁的释放,是在第20行就执行了。
那么就会出现一个问题,锁已经释放了,但是事务还没提交。这时候其他的线程在并发请求过来的时候:
一锁。拿锁可以拿到,因为锁被释放了
二查。查询数据也查不到,因为这时候之前的那个事务可能还没提交,未提交的数据,新的事务是看不到的。
三更新。执行更新操作,导致数据重复。
以上,就是因为事务的粒度大于锁的粒度,并且因为事务之间隔离性,导致并发了。
想要解决这个问题,就是减小事务的粒度,或者增大锁的粒度,不要在事务提交前就把锁释放掉。
减小事务粒度,这时候就可以把声明式事务改成编程式事务,自己控制锁的粒度:
@Autowired
private TransactionTemplate transactionTemplate;
public boolean order(Request request) {
RLock lock = redisson.getLock(request.getIdentifier());
try {
//一锁
lock.lock();
//二查
OrderDo order = orderMapper.find(request.getIdentifier());
if (order!=null) {
resp.setResultFail(ReturnCodeEnum.USER_EXIST);
return false;
}
transactionTemplate.execute(new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(TransactionStatus status) {
//三更新,保存订单数据
orderMapper.insertOrder(request);
//三更新,保存订单流水
orderStreamMapper.insertOrder(request);
//三更新,保存事件流水
eventMapper.insert(request);
}
});
return true;
}finally {
redisLock.unLock();
}
}