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

千万不要在事务内加锁

作者: 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("乐观锁更新失败");
            }

        }
    }

相关文章

  • 千万不要在事务内加锁

    今天线上出现了一个故障,用户充值成功后,账户的充值记录一直没有变更.第一个反应,就是支付公司没有回调,但是通过查询...

  • springboot 的事务与锁

    最近有一个需求,需要在事务的基础上增加锁,实现同步,过程中使用到了springboot的注解和synchroniz...

  • InnoDB 事务加锁分析

    本文首发于 vivo互联网技术 微信公众号链接:https://mp.weixin.qq.com/s/S7Mhls...

  • 【学习】MySQL数据库

    存储引擎 存储引擎 索引 InnoDB索引原理索引 锁 锁不同Select加锁分析 事务 事务事务隔离级别XAMV...

  • Mysql 隔离级别与锁的关系

    Innodb中的事务隔离级别和锁的关系MySQL加锁处理分析

  • 谈谈mysql锁机制及原理

    加锁是实现数据库并发控制的一个非常重要的技术。当事务在对某个数据对象进行操作前,先向系统发出请求,对其加锁。加锁后...

  • 2. 事务锁与语句锁冲突吗?

    数据库只对原子操作(Sql语句与事务)加锁。大体如下: 问题:事务本来包含多个单句,那么事务加了锁,单句还要加吗?...

  • service层不能加锁,可能会无效

    因为spring的事务是通过AOP实现的,因此在service中加锁,锁在事务内部开启的,放锁后,事务可能还没提交...

  • 【转】InnoDB 事务加锁分析

    事务的四种隔离级别 1、未提交读(Read uncommitted) 一个事务读取到其他事务未提交的数据,是级别最...

  • mysql 的 事务 和 锁

    mysql事务select 只是进行了隔离,保证数据的一致性,并没有加锁,如果要加锁可以用for update。 ...

网友评论

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

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