MySql可重复读事务隔离级别下造成数据不一致的错误使用方式

MySql有四种事务隔离级别,默认且常用的是可重复读(REPEATABLE-READ)。除了串行化级别外,其它三种级别在数据一致性方面都有或多或少的问题。自然的,不正确地使用可重复读隔离级别,也会引发数据不一致问题。

在排查一个重复提交问题时,发现了一个觉得“不太可能”的问题。现用伪代码的方式还原这个问题:

//*入口方法*//
modifyStaus(:id){
    
    //第一次查询
    Object obj=dao.select("select id,is_over,update_item_id from test_thread where id=:id");
    //is_over只有两个值0和1
    if(is_over==0){
        //。。。省略一堆很占用时间逻辑
    
        //变更状态为结束
        int count=dao.update("update test_thread set is_over=1 where id=:id");  
        //调用另一个方法,备注这很可能是其它小组的同事写的一个接口
        notifyOver(:id);      
    }
    
    //。。。省略后续的逻辑
}

/*状态为结束时才调用*/
 notifyOver(:id){
    //第二次查询
    Object obj=dao.select("select id,is_over,update_item_id from test_thread where id=:id");
    if(obj.is_over!=1){
        return;//状态不对
    }
    //。。。后续的逻辑
}

代码常见这种结构,即“查询-更新-再查询”,也即是重复读了一次。MySql可重复读的基本含义是:在同一事务内,只要某条记录没在本事务内修改数据,那么不管查询多少次,数据内容都与第一次查询时的内容一样。上面的代码执行后,原本以为notifyOver方法中查询到的is_over的值一定是1,实际并发运行后is_over的值可能是0,也可能是1。起初也觉得奇怪,毕竟调用过update操作,第二次查询应该是update之后的值。

这个问题比较不通情理,所以直接用Mysq命令行模拟了两个线程进行“查询-更新-再查询”的操作,最后得出结论:只有某条记录被成功更新(即update返回值为1)时,第二次查询才会查到(本事务内)更新之后的最新值,如果update语句返回值是0,那么后续查询仍然与第一次查询内容一样。这即是可重复读隔离级别的含义。

Mysql命令行测试:(is_over的初值是0)

Mysql命令行测试结论

A线程执行完第1步后,只要被B线程抢先执行完commit,在A线程未提交事务之前查到的is_over永远是0。

究其原因还是AB两个线程对这条记录更新的内容完全一样,也就是只能有一个线程的update返回更新记录数是1。如果A线程执行update test_thread set is_over=10 where id=1;那么A线程第3步查到的is_over是10而不是0。

备注:以上命令在Mysql5.7和Mysql8.0测试结果一致。

最后总结:代码中执行update,一定要处理返回值。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值