Skip to content

面试速答(先看这里)

**一句话结论:**其实这个问题本质就是锁的粒度和事务的粒度之间的关系,到底谁应该更大一些?

60秒标准回答:

在使用分布式锁与事务注解(如 Spring 的 @Transactional)一起时,分布式锁的位置(锁在事务外还是事务内)对于程序的行为和事务的一致性具有重要的影响

其实这个问题本质就是锁的粒度和事务的粒度之间的关系,到底谁应该更大一些?

即在加锁之后,再调用一个带事务注解的方法,伪代码如下

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

回答主线:

  • **锁的粒度大于事务粒度:**即在加锁之后,再调用一个带事务注解的方法,伪代码如下:
  • **事务的粒度大于锁粒度:**即在开启事务之后,再进行加锁,伪代码如下:

**记忆锚点:**Redis → Transactional → 对于程序的行为和事务 → 再调用一个带事务注解 → 那么就会导致拖长事务 → 锁在事务外还是事务

关键取舍:

  • 锁的粒度大于事务粒度 即在加锁之后,再调用一个带事务注解的方法,伪代码如下: 这个方式的 优点是事务的时长不受锁的影响。
  • 事务的粒度大于锁粒度 即在开启事务之后,再进行加锁,伪代码如下: 这个方式的 优点是锁的时长比较短,并发度会更高一些。
  • 因为相比于数据库资源来说,我们使用的Redis资源要更加成本低。

易错提醒:

  • 虽然这样可能会让你的锁粒度变大,但是可有效的避免数据库链接被占用、以及导致数据不一致等问题。

加分表达:

  • 其实这个问题本质就是锁的粒度和事务的粒度之间的关系,到底谁应该更大一些?
  • 如下面这个例子: 建议大家在同时使用锁和事务的时候,考虑先加锁然后再加事务。

追问准备:

  • 围绕「Redis」:底层原理是什么?使用时有哪些边界和常见坑?
  • 围绕「Transactional」:底层原理是什么?使用时有哪些边界和常见坑?
  • 围绕「对于程序的行为和事务」:底层原理是什么?使用时有哪些边界和常见坑?
  • 如果线上出现异常,你会如何定位、验证并规避?

典型回答 ​

在使用分布式锁与事务注解(如 Spring 的 @Transactional)一起时,分布式锁的位置(锁在事务外还是事务内)对于程序的行为和事务的一致性具有重要的影响。

其实这个问题本质就是锁的粒度和事务的粒度之间的关系,到底谁应该更大一些?

锁的粒度大于事务粒度 ​

即在加锁之后,再调用一个带事务注解的方法,伪代码如下:

java
lock.lock();
try {
    @Transactional
    public void method() {
        // 事务性操作
    }
} finally {
    lock.unlock();
}

这个方式的优点是事务的时长不受锁的影响。一般来说我们的分布式锁都是通过Redis等实现的,这就涉及到一个远程调用,那么就会导致拖长事务的问题。

📄 ✅为啥不要在事务中做外部调用?

打开文档:✅为啥不要在事务中做外部调用?

缺点就是锁的时间会更长,跨越了整个事务。因为锁的时间更长了,那么整个系统的吞吐量也就会更低了。

事务的粒度大于锁粒度 ​

即在开启事务之后,再进行加锁,伪代码如下:

java
@Transactional
public void method() {
    lock.lock();
    try {
        // 事务性操作
    } finally {
        lock.unlock();
    }
}

这个方式的优点是锁的时长比较短,并发度会更高一些。

缺点就是在事务中进行了外部调用,可能会拖长事务、占用数据库链接等问题。

这个方式还有一个重要的问题,那就是可能会导致数据不一致。如下面这个例子:

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

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

如何选择 ​

建议大家在同时使用锁和事务的时候,考虑先加锁然后再加事务。虽然这样可能会让你的锁粒度变大,但是可有效的避免数据库链接被占用、以及导致数据不一致等问题。

因为相比于数据库资源来说,我们使用的Redis资源要更加成本低。

还有就是一般来说,加锁时间范围稍微大一点,影响也并不是很大。因为加锁主要是解决并发问题的,悲观一点思想是可以接受的。而且本来我们通常加锁都有各种续期的机制,加锁的时长就是会长一些的,所以这个因为粒度不同导致的时长增加其实都可以忽略不计的。