2021-02-05-事务和锁在同一方法中的问题

本文探讨了一个Spring框架下关于事务管理和并发问题的案例,通过分析代码中潜在的问题,提出了解决方案,包括去除事务、使用外部锁与内部事务结合,以确保线程安全并提高吞吐量。

常规流程

@Transactional(rollbackFor = Exception.class)    
public Boolean pay(PayRequest payRequest) {

        User dbUser = userService.findById(payRequest.getUserId());
        UserPayAmount userPayAmount = new UserPayAmount();
        userPayAmount.setUserId(payRequest.getUserId());
        userPayAmount.setOrderId(payRequest.getOrderId());
        userPayAmount.setBeforeAmount(dbUser.getAmount());
        userPayAmount.setChangeAmount(payRequest.getPayAmount());
        BigDecimal afterChageAmount = dbUser.getAmount().subtract(payRequest.getPayAmount());
        userPayAmount.setAfterAmount(afterChageAmount);

        dbUser.setAmount(afterChageAmount);
        userService.update(newUser);
        userPayAmountRepository.save(userPayAmount);
        return true;
    }
}

上面代码看上去没问题,但是存在很大的问题。

 

事务问题


        dbUser.setAmount(afterChageAmount);
        userService.update(newUser);

这段代码有问题。去掉userSer

并发下问题

 

使用Jmeter压一下。

 

初始值:10000

随机值:1~11

线程数:50

循环数:30

 

DELETE FROM user_pay_amount;

UPDATE `user`  u set u.amount = 10000 WHERE id = 1;



SELECT user_pay_amount.before_amount,count(before_amount) FROM user_pay_amount GROUP BY before_amount;


SELECT count(DISTINCT(before_amount)) FROM user_pay_amount;

 

image.png

 

 

解决方案

锁住整个方法(有误)

实战

public synchronized Boolean pay(PayRequest payRequest) {
}
# 108
SELECT count(DISTINCT(before_amount)) FROM user_pay_amount;

,虽然数量增大,但是还是存在线程安全问题

 

原因

 

锁释放了,但是事务还未提交,下个线程查询到的还是旧的数据

 

spring事务使用的是aop,底层是动态代理。

 

https://blog.youkuaiyun.com/sinat_36454672/article/details/103353211

 

解决方法

去掉事务

    public synchronized Boolean pay(PayRequest payRequest) {
        dbUser.setAmount(afterChageAmount);
        //去掉事务这里要显示调用
        userService.update(newUser); 
        userPayAmountRepository.save(userPayAmount);
        return true;
    }

//最好这样写
        
        User newUser = new User();
        BeanUtils.copyProperties(dbUser, newUser);
        newUser.setAmount(afterChageAmount);
        userService.update(newUser);

去掉事务,结果正常

image.png

 

去掉事务后遗症

但是去掉事务,就无法保证用户余额是能被正确修改。

 

如下的事务起作用了么?

    public Boolean payInTx(PayRequest payRequest) {
        synchronized (this){
            return pay(payRequest);
        }

    }
    @Transactional
    public  Boolean pay(PayRequest payRequest) {

        User dbUser = userService.findById(payRequest.getUserId());
        if (dbUser.getAmount().compareTo(BigDecimal.ZERO) < 0) {
            throw new RuntimeException("余额不足");
        }
        UserPayAmount userPayAmount = new UserPayAmount();
        userPayAmount.setUserId(payRequest.getUserId());
        userPayAmount.setOrderId(payRequest.getOrderId());
        userPayAmount.setBeforeAmount(dbUser.getAmount());
        userPayAmount.setChangeAmount(payRequest.getPayAmount());
        BigDecimal afterChageAmount = dbUser.getAmount().subtract(payRequest.getPayAmount());
        userPayAmount.setAfterAmount(afterChageAmount);
       //如果事务起作用,那么应该还是150条,实际去重后是1,表示事务并没有起作用
        dbUser.setAmount(afterChageAmount);
        userPayAmountRepository.save(userPayAmount);
        return true;
    }

 

还是不行,事务起作用,但是锁没有起作用

 @Transactional
    public Boolean payInTx(PayRequest payRequest) {
        synchronized (this){
            return pay(payRequest);
        }

    }
    @Transactional
    public  Boolean pay(PayRequest payRequest) {
        dbUser.setAmount(afterChageAmount);
        return true;
    }
    @Transactional
    public Boolean payInTx(PayRequest payRequest) {
        return pay(payRequest);
    }
    public synchronized Boolean pay(PayRequest payRequest) {
        return true;
    }

 

 

以上,事务生效,但是锁都没有作用

 

最终:

 

新建一个类,在外部使用锁,在内部使用事务。但是吞吐量比较少

外部锁方法

public class UserPayAmountManage {

    private final UserPayAmountService userPayAmountService;

    public synchronized Boolean synchronizedPay(PayRequest payRequest) {
        return userPayAmountService.pay(payRequest);
    }
}

内部事务

 @Transactional
    public Boolean pay(PayRequest payRequest) {

        User dbUser = userService.findById(payRequest.getUserId());
        if (dbUser.getAmount().compareTo(BigDecimal.ZERO) < 0) {
            throw new RuntimeException("余额不足");
        }
        UserPayAmount userPayAmount = new UserPayAmount();
        userPayAmount.setUserId(payRequest.getUserId());
        userPayAmount.setOrderId(payRequest.getOrderId());
        userPayAmount.setBeforeAmount(dbUser.getAmount());
        userPayAmount.setChangeAmount(payRequest.getPayAmount());
        BigDecimal afterChageAmount = dbUser.getAmount().subtract(payRequest.getPayAmount());
        userPayAmount.setAfterAmount(afterChageAmount);

        dbUser.setAmount(afterChageAmount);

//        User newUser = new User();
//        BeanUtils.copyProperties(dbUser, newUser);
//        newUser.setAmount(afterChageAmount);
//        userService.update(newUser);
        userPayAmountRepository.save(userPayAmount);
        return true;
    }

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值