美文网首页
千万不要在事务内加锁

千万不要在事务内加锁

作者: sT丶 | 来源:发表于2022-07-15 16:29 被阅读0次

    今天线上出现了一个故障,用户充值成功后,账户的充值记录一直没有变更.
    第一个反应,就是支付公司没有回调,但是通过查询日志发现,回调时有的,只是在处理时,使用乐观锁更新账户余额失败了!
    WHAT? 我不是加了分布式锁吗,为什么还会出现这个问题.

    伪代码如下

    @Transactional(rollbackFor = Exception.class)
        public void add(Param param) {
            // 获取redis 锁
            boolean lock = redisLock.getLock();
            if(!lock){
                throw new BaseException("获取锁失败");
            }
            // 获取账号信息
            UserAccount userAccount = getUserAccount(param.userId);
    
            // 更新
            userAccount.setBalance = (userAccount.getBalance + param.amount);
            userAccount.setDataVersion(userAccount.getDataVersion()+1);
    
            // 乐观锁更新
            if(!update(userAccount)){
                throw new BaseException("乐观锁更新失败");
            }
    
            // 解锁
            redisLock.unlock();
        }
    

    通过日志发现,是在乐观锁更新时失败了,不可能呀,我明明先获取的锁,再去获取的账户信息,为什么还会更新失败呢?

    最终经过脑洞思考和排查,发现了问题,详细的可以看下面的流程图.

    image.png

    问题的原因就是在没有提交事务的时候,提前释放了锁,导致其余的线程获取到的是同一个乐观锁版本的数据,最终更新时,导致乐观锁失败.

    解决的办法,也很简单,可以将账户操作放在一个新的类方法里面,在这里进行事务操作.同时在原来的方法里面加锁调用.这样就可以解决了.

    class A{
            
            public void addAndLock(Param param){
                // 获取redis 锁
                boolean lock = redisLock.getLock();
                // 执行操作
                B.add(param);
                // 解锁
                redisLock.unlock();
            }
        }
      
    
        class B{
            @Transactional(rollbackFor = Exception.class)
            public void add(Param param) {
    
                if(!lock){
                    throw new BaseException("获取锁失败");
                }
                // 获取账号信息
                UserAccount userAccount = getUserAccount(param.userId);
    
                // 更新
                userAccount.setBalance = (userAccount.getBalance + param.amount);
                userAccount.setDataVersion(userAccount.getDataVersion()+1);
    
                // 乐观锁更新
                if(!update(userAccount)){
                    throw new BaseException("乐观锁更新失败");
                }
    
            }
        }
    

    相关文章

      网友评论

          本文标题:千万不要在事务内加锁

          本文链接:https://www.haomeiwen.com/subject/uiqcirtx.html