spring事务方法的同步问题

一、事务方法的同步问题

例如以下这段代码

    @RequestMapping("/test")
    @Transactional
    public int test(){
        Test test = testMapper.selectById(1);
        int max = test.getMax() + 1;
        test.setMax(max);
        testMapper.updateById(test);
        return max;
    }

当用jmeter并发访问1000次这个接口,发现max的值为532,跟预期的1000不一样。

这是因为有些请求查询不到数据库最新的值,导致的同步问题。

 

这里我使用的项目是springboot+mybatisplus+mysql

 

 

二、解决方法

1.使用select for update解决

     @Select("select * from test where id = #{id} for update")
     Test selectForUpdate(@Param("id") long id);

for update是一种行级锁,又叫排它锁,一旦用户对某个行施加了行级加锁,则该用户可以查询也可以更新被加锁的数据行,其它用户只能查询但不能更新被加锁的数据行.如果其它用户想更新该表中的数据行,则也必须对该表施加行级锁.即使多个用户对一个表均使用了共享更新,但也不允许两个事务同时对一个表进行更新,真正对表进行更新时,是以独占方式锁表,一直到提交或复原该事务为止。行锁永远是独占方式锁。

只有当出现如下之一的条件,才会释放共享更新锁:
1、执行提交(COMMIT)语句
2、退出数据库(LOG OFF)
3、程序停止运行

 

 

2.使用手动事务+同步锁synchronized

    @Autowired
    private PlatformTransactionManager platformTransactionManager;

    @Autowired
    private TransactionDefinition transactionDefinition;


    @RequestMapping("/test")
    public synchronized int test(){
        int max = 0;
        TransactionStatus transactionStatus = platformTransactionManager.getTransaction(transactionDefinition);
        try{
            Test test = testMapper.selectById(1);
            max = test.getMax() + 1;
            test.setMax(max);
            testMapper.updateById(test);
            platformTransactionManager.commit(transactionStatus);
        }catch (Exception e){
            platformTransactionManager.rollback(transactionStatus);
        }
        return max;
    }

这里使用手动事务的原因是,spring aop中,在方法执行前开启事务,方法执行后提交事务,也就是说事务的开启和提交过程是没有加锁的,因此无法实现同步。

 

 

以下方式是无法实现同步的:

    @RequestMapping("/test")
    @Transactional
    public synchronized int test(){
        Test test = testMapper.selectById(1);
        int max = test.getMax() + 1;
        test.setMax(max);
        testMapper.updateById(test);
        return max;
    }

将数据库值清0,再次使用jmeter测试,发现max的值为647。

说明依然没有解决同步问题。

 

就算把synchronized放到方法体里也是一样无法实现同步的,锁住的只是方法里的代码块,没有锁住事务的开启和提交过程。

    private Object object = new Object();
    
    @RequestMapping("/test")
    @Transactional
    public int test(){
        int max = 0;
        synchronized (object) {
            Test test = testMapper.selectById(1);
            max = test.getMax() + 1;
            test.setMax(max);
            testMapper.updateById(test);
        }
        return max;
    }

 

 

 

3.使用乐观锁

数据库添加version字段。

public class Test {
    private Long id;
    private Integer max;
    @Version
    private Integer version;

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public Integer getMax() {
        return max;
    }

    public void setMax(Integer max) {
        this.max = max;
    }

    public Integer getVersion() {
        return version;
    }

    public void setVersion(Integer version) {
        this.version = version;
    }
}

 

    @RequestMapping("/test")
    @Transactional(isolation = Isolation.READ_COMMITTED)
    public int test(){
        int i = 0;
        Test test = null;
        while(i<1){
            test = testMapper.selectById(1);
            test.setMax(test.getMax()+1);
            i = testMapper.updateById(test);
        }

        System.out.println(test.getMax());
        return test.getMax();
    }

这里因为使用了mybatisplus框架,只需要在bean类version属性添加@Version注释,然后使用框架提供的方法查询和修改,默认支持乐观锁。

需要注意的是,这里@Transactional注释中标明了隔离级别为读取已提交。

否则会导致在事务中查询不到数据库最新的数据,导致一直无法更新成功,一直回旋。

原因是@Transactional默认的隔离级别是根据数据库的隔离级别,由于我用的是mysql的innodb引擎,默认的隔离级别是支持可重复读的,导致在事务中,查询数据库的值都是一致的可重复读的,导致无法获取数据库最新的值。

因此将事务隔离级别改为读取已提交就可以解决

 

 

三、总结

事务方法的同步可以使用以下方法解决:

select for update

手动事务+同步锁synchronized(可将锁细化)

③乐观锁

 

并发量不是特别高的话,使用乐观锁的效率最高。

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值