在使用分布式锁与事务注解(如 Spring 的 @Transactional)一起时,分布式锁的位置(锁在事务外还是事务内)对于程序的行为和事务的一致性具有重要的影响。
其实这个问题本质就是锁的粒度和事务的粒度之间的关系,到底谁应该更大一些?
即在加锁之后,再调用一个带事务注解的方法,伪代码如下:
lock.lock();
try {
@Transactional
public void method() {
// 事务性操作
}
} finally {
lock.unlock();
}
这个方式的优点是事务的时长不受锁的影响。一般来说我们的分布式锁都是通过Redis等实现的,这就涉及到一个远程调用,那么就会导致拖长事务的问题。
缺点就是锁的时间会更长,跨越了整个事务。因为锁的时间更长了,那么整个系统的吞吐量也就会更低了。
即在开启事务之后,再进行加锁,伪代码如下:
@Transactional
public void method() {
lock.lock();
try {
// 事务性操作
} finally {
lock.unlock();
}
}
这个方式的优点是锁的时长比较短,并发度会更高一些。
缺点就是在事务中进行了外部调用,可能会拖长事务、占用数据库链接等问题。
这个方式还有一个重要的问题,那就是可能会导致数据不一致。如下面这个例子:
建议大家在同时使用锁和事务的时候,考虑先加锁然后再加事务。虽然这样可能会让你的锁粒度变大,但是可有效的避免数据库链接被占用、以及导致数据不一致等问题。
因为相比于数据库资源来说,我们使用的Redis资源要更加成本低。
还有就是一般来说,加锁时间范围稍微大一点,影响也并不是很大。因为加锁主要是解决并发问题的,悲观一点思想是可以接受的。而且本来我们通常加锁都有各种续期的机制,加锁的时长就是会长一些的,所以这个因为粒度不同导致的时长增加其实都可以忽略不计的。