✅用了一锁二查三更新,为啥还出现了重复数据?

典型回答

✅如何解决接口幂等的问题?

不懂什么是一锁二查三更新的,先看上面这个问题↑↑↑。

那么,用这个这个方式做幂等防控,就一定不会出现脏数据了么?先看下以下代码:

@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();
    }
}

原文: https://www.yuque.com/hollis666/xkm7k3/rc0qez0gyn9t62f9