记一次for update“同一事务”中update无法获取数据锁的解决

背景

银行多个异步通知先后到达,需要依次更新同一条数据A(wherte acountId=aaa)(acountId是唯一索引)的不同状态,每一次更新需要在上一次更新的基础上进行。

说明:数据A(wherte acountId=aaa)原本状态status=0、openstatus=0
  ——》在收到通知1后,在方法methodA中,将状态更新为status=1、openstaus=0,注更新前需要查询到数据A(wherte acountId=aaa)
  ——》在收到通知2后,在方法methodB中,将状态更新为status=1、openstaus=1或者status=1、openstaus=2,注更新前需要查询到数据A(wherte acountId=aaa)

注:1、数据A的大概字段:sysNo(主键)、accountId(唯一键)、status、openStatus、createTime、…
  2、在进行某一项交易后,通知1和通知2会先后到来,但是先后的时间差不能保证,那么存在两种情况:情况A,通知2来时执行方法methodB时,可能通知1的处理方法methodA已经执行完毕;情况B,通知2来时执行方法methodB时,也可能methodA方法还在执行中。

为了防止情况B在methodB开始执行的时候,methodA还未执行完毕导致的数据更新不是在最新数据上进行的更新(专业术语:更新丢失或者更新覆盖),及methodB是直接在原始数据上进行的更新,而不是在methodA更新后的数据上进行更新的。为了防止更新丢失,可以有多种解决办法:悲观锁(for update)方式、乐观锁(版本号、时间戳)方式、分布式锁(redis,zookeeper)方式等。

问题

由于公司项目并发不高,所以直接使用悲观锁来简单处理的,但是在处理过程中发现一个奇怪的现象(注该现象是只在开发和测试环境中出现),如下图:
在这里插入图片描述

描述

在同一个事务中,第一步通过for update获取到的排他锁,但是在进行更新update的时候发现该笔数据的排它锁却无法获取,是不是感觉很奇怪?

于是下面就开始找问题,为什么update获取不到上面的同一笔数据的锁。
在这里插入图片描述
一、定位数据更新update语句,是不是更新到了索引?检查了update语句,在update语句中并没有更新到索引字段。

二、在数据库中直接执行是否报错?数据库中直接执行没有问题,说明代码中前后执行不是在同一个事务中(定位问题)。
在这里插入图片描述
三、数据库事务是不是统一管理的?数据库索引会不会存在一部分spring管理,一部分mybatis管理?确认了数据库事务统一由Spring管理,所以不是该原因造成的。

四、是否使用主从数据库导致数据来源不一致
  描述:公司生产上使用主从数据库,本地开发和测试环境都是单一数据库,没有主从,但是通过ShardingJDBC配置了主从设置
在这里插入图片描述
问题原因就在这,虽然测试环境实际上只有一个数据库,但是ShardingJDBC配置了主从,那么即使只有一个数据库,也会产生两条连接,一条用于主、一条用于从,所以在select for update语句的时候默认使用从库的链接的事务中获取数据,并且将数据加锁,而表面上看代码是同一事务的update时,实际上是使用主库的链接的事务进行更新数据,所以select 和 update不在同一个事务中,故代码层面看是同一事务的先后两个操作,update时实际获取不到之前的select的锁,所以发生等待直至超时报错。

解决办法:强制统一使用主链接读取和修改数据,代码如下:
在这里插入图片描述

补充:

1、悲观锁导致的死锁
在使用悲观锁的过程中,注意加锁顺序,如果methodA方法是更新数据1和数据2,而methodB是更新数据2和数据1,如果两个线程同时进入到methodA和methodB,分别对数据1和数据2上锁,然后又分别等待数据2和数据1释放锁后自己获取,那么就会造成死锁。

避免死锁的方法:
  A:统一加锁顺序,例如按照id自然序来进行加锁操作,这样事务之间的加锁操作就不会存在死锁。
  B:不使用悲观锁或者在使用悲观锁前在加一个全局锁,如在redis存储一个lock标记,当事务获取到这个lock标记时,才允许进行更新操作,否则等待锁。

2、ShardingJDBC
  当当网开发的主要用于处理数据分片和读写分离的框架。

该文章来自于本人多年前发表于博客园的原创作品:《记一次for update“同一事务”中update无法获取数据锁的解决》,转载请注明出处。

  • 7
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值