Mysql事务隔离导致的问题

这个功能是要产生一个顺序增长的流水号。简化的代码如下:


 @Transactional(readOnly=false, propagation=Propagation.REQUIRED)
    public RunningNumber getNextNumber(String runningType){
        RunningNumberType type = config.getType(runningType);
        
        RunningNumber runningNumber = null;
        synchronized (getClass()) {
            do {
                runningNumber = dao.queryRunningNumber(type.getType());
                
                if (runningNumber == null) {
                    runningNumber = new RunningNumber();
                    runningNumber.setRunningType(type.getType());
                    runningNumber.setRunningNumber("1");
                    try {
                        dao.insert(runningNumber);
                    } catch (Exception e) {
                        runningNumber = null;
                    }
                } else {
                    String number = runningNumber.getRunningNumber();
                    runningNumber.setRunningNumber(""+(new Long(number) + 1));
                    if (dao.update(runningNumber) == 0) {
                        runningNumber = null;
                    }
                }
            } while (runningNumber == null);
        }
        
        runningNumber.setRunningDate(runningNumber.getRunningDate().trim());
        
        return runningNumber;

    }

这个逻辑也很简单,就是获取一个新的流水号。如果失败了就再次尝试直到成功。


为了解决多服务器时的冲突问题,流水号表使用了版本号控制的乐观锁。即修改时运行的SQL如下:

update T_RUNNINGNUMBER set version = version + 1, running_Type=?, running_Number=? where ID=? and version=?

这样如果有修改冲突情况下,修改会失败。


上面的程序逻辑看起来应该没问题。为了避免同一个VM中多线程间的冲突,还synchronize了关键代码。但是在多线程访问的情况下,这段程序会陷入死循环。为什么会死循环呢?我百思不得其解。


最后我终于想明白了原因。这是事务隔离导致的问题。Mysql数据库缺省的事务隔离是REPEATABLE_READ,即同一个事务中多次读同一条记录,读出来的值是一样的。如果线程A和B同时调用getNextNumber方法,假设这时目标记录的version是72,再假设线程A先进入了同步代码段,成功地生成了一个新流水号,然后退出这个方法,结束了事务。这时数据库里目标记录的version变成了73。然后线程B进入了同步代码段,但是由于事务隔离是REPEATABLE_READ,导致线程B读到的version始终是72,也就是说线程B永远都不可能成功地生成新流水号,将陷入死循环。


原因明白了,解决方法也就清楚了。降低事务的隔离级别,设为READ_COMMITTED,即读取已提交事务,就可解决这个问题。同时事务传递也要改为REQUIRES_NEW。为什么必须改为REQUIRES_NEW呢?如果是REQUIRED,那么这个方法有可能被另外一个定义事务的方法调用,此方法定义的事务隔离级别就不起作用了。为了保证正确的隔离级别,这里必须启用一个新的事物。

@Transactional(readOnly=false, propagation=Propagation.REQUIRES_NEW, isolation=Isolation.READ_COMMITTED)
	public String getNextNumber(String runningType){

这个bug也提醒了我们,事务的缺省隔离级别并非放之四海而皆准的,有时候还是需要根据具体的业务场景设置合适的隔离级别的。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值