Skip to content

面试速答(先看这里)

**一句话结论:**首先,我们可以看到,代码中通过@Transactional在order方法上增加了一个事务,事务的范围是整个方法,也就是方法全部执行完,事务才会提交。

60秒标准回答:

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

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

以上代码,其实是有可能出现脏数据的

**答题顺序:**结论 → 原理/机制 → 关键流程 → 场景与取舍 → 易错点

回答主线:

  • **要点1:**不懂什么是一锁二查三更新的,先看上面这个问题↑↑↑。
  • **要点2:**那么,用这个这个方式做幂等防控,就一定不会出现脏数据了么?
  • **要点3:**也就是说,代码完第28行之后,事务才会提交。
  • **要点4:**那么就会出现一个问题,锁已经释放了,但是事务还没提交。
  • **要点5:**查询数据也查不到,因为这时候之前的那个事务可能还没提交,未提交的数据,新的事务是看不到的。

**记忆锚点:**Transactional → 方法上增加了一个事务 → 事务的范围是整个方法 → 不懂什么是一锁 → 改成编程式事务 → 的粒度大于锁

易错提醒:

  • 想要解决这个问题,就是减小事务的粒度,或者增大锁的粒度,不要在事务提交前就把锁释放掉。

追问准备:

  • 围绕「Transactional」:底层原理是什么?使用时有哪些边界和常见坑?
  • 围绕「方法上增加了一个事务」:底层原理是什么?使用时有哪些边界和常见坑?
  • 围绕「事务的范围是整个方法」:底层原理是什么?使用时有哪些边界和常见坑?
  • 如果线上出现异常,你会如何定位、验证并规避?

典型回答 ​

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

打开文档:✅如何解决接口幂等的问题?

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

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

java
@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行就执行了。

那么就会出现一个问题,锁已经释放了,但是事务还没提交。这时候其他的线程在并发请求过来的时候:

一锁。拿锁可以拿到,因为锁被释放了

二查。查询数据也查不到,因为这时候之前的那个事务可能还没提交,未提交的数据,新的事务是看不到的。

三更新。执行更新操作,导致数据重复。

以上,就是因为事务的粒度大于锁的粒度,并且因为事务之间隔离性,导致并发了。

想要解决这个问题,就是减小事务的粒度,或者增大锁的粒度,不要在事务提交前就把锁释放掉。

减小事务粒度,这时候就可以把声明式事务改成编程式事务,自己控制锁的粒度:

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