目录

分布式锁的使用——不要把锁加在事务内!

✅用了"一锁二判三更新",但是幂等被击穿

code 案例

在我们的项目中,我们很多地方都会为了避免并发,增加分布式锁,并且会采用一锁、二判、三更新的方式实现一个幂等的逻辑。

同时,为了方便大家使用分布式锁,我们自己定义了一个@DistributeLock的直接。也是就有以下代码:

同一个方法中,增加了多个注解,同时有@DistributeLock和@Transactional 两个注解。

这种情况在,我们自定义的注解@DistributeLock的切面默认会在最后执行,于是这段代码就是会先执行事务,然后再执行加锁。

最终的逻辑就像下面这段代码一样:

csharp 复制代码
@Transactional(rollbackFor = Exception.class)
public boolean register(Request request) {
    RLock lock = redisson.getLock(request.getIdentifier());
    try {
        //一锁       
        lock.lock();
        //二查        
        User user = userMapper.find(request.getIdentifier());
        if (user != null) {
            return false;
        }
        //三更新,保存订单数据    
        userMapper.insertOrder(request);
    } finally {
        lock.unlock();
    }
    return true;
}

按照这个顺序执行的:

  1. 进入事务
  2. 加锁
  3. 解锁
  4. 事务提交

这时候就会出现一种情况,在第三步和第四步中间,如果有一个其他的线程也调用这个 register 方法了。

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

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

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

三更新。执行更新操作,导致数据重复或者报错。

这就是我们需要解决的问题,那么看上去就是事务的切面执行顺序的问题,我们应该让锁的粒度大于事务的粒度就能解决了这个问题了。

那么,就想办法让分布式锁的注解的切面先执行。解决办法就是借助@Order 注解,他可以直接用在切面类上,用于指定切面的执行顺序。值越小,优先级越高,切面会越早执行。

所以,修改后的分布式锁的切面类如下:

新增 Order 注解,把他的优先级设置为最小值,即优先级最高,最先开始执行即可。

总结

在使用分布式锁的时候,习惯性的尽量缩小同步代码块的范围

但是如果数据库隔离级别是可重复读,这种情况下不要把分布式锁加在@Transactional注解的事务方法内部。

因为可能会出现这种情况:

线程1开启事务A后获取分布式锁,执行业务代码后在事务内释放了分布式锁。这时候线程1开启了事务B获取到了线程1释放的分布式锁,执行查询操作时查到的数据就可能出现问题。因为此时事务A是在事务内释放了锁,事务A本身还没有完成提交

本文是转载文章,点击查看原文
如有侵权,请联系 xyy@jishuzhan.net 删除
相关推荐
Asthenia04121 分钟前
Mybatis:Configuration/MappedStatement/双缓存/XML的id/分页机制实现/自增主键id/xml罕见标签
后端
Aska_Lv13 分钟前
业务设计---不用redis分布式锁, 如何防止用户重复点击?
后端·架构
Asthenia04121 小时前
Mybatis:结构/优缺点/SQL结果如何封装/动态SQL/#和$/dao层原理
后端
一名用户1 小时前
盘点shell中对数以万计的IT人来说非常重要的特殊变量!
后端·shell
Asthenia04121 小时前
JDBC:脏读/execute与executeQuery/update的区别/jdbc的不足/ResultSet
后端
未完结小说1 小时前
Dubbo的基础教程
java·后端
钢枪的代码1 小时前
RocketMQ如何实现消息Exactly-Once
后端
Asthenia04121 小时前
JDBC:数据库连接池/JDO/JDBC最佳实践/如何连接数据库/事务处理/程序与JDBC松耦合
后端
bobz9651 小时前
strongswan ipsec 多兴趣流子网配合源进源出策略测试
后端
Asthenia04123 小时前
JDBC是什么?/Driver的意义/预编译和普通statement/CallableStatement
后端