主题
面试速答(先看这里)
**一句话结论:**其实这个问题本质就是锁的粒度和事务的粒度之间的关系,到底谁应该更大一些?
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资源要更加成本低。
还有就是一般来说,加锁时间范围稍微大一点,影响也并不是很大。因为加锁主要是解决并发问题的,悲观一点思想是可以接受的。而且本来我们通常加锁都有各种续期的机制,加锁的时长就是会长一些的,所以这个因为粒度不同导致的时长增加其实都可以忽略不计的。