记录高并发事务中Update导致MySQL死锁的问题

背景:线上运营在后台导入发券给用户的excel后,服务端触发异步、并行发券操作

问题代码:

@Transactional(rollbackFor = Exception.class) 
public boolean sendCouponToMoreUser(Date date, CouponToMoreUserMsg couponMsg) {
    //1.查询优惠券的基础信息(select 优惠券表)
    //2.更新优惠券基础信息中的剩余数量 (update 优惠券表)
    //3.发券 (insert 用户领取优惠券表)
}

第一步,执行select时,需要给当前优惠券行加一个“Shared Lock”共享锁,共享锁是允许多个session同时进入的
第二步,执行update时,需要给当前优惠券行加一个“Exclusive Lock”独占锁

当两个session并发时,两个session执行select时都获得了“Shared Lock”,而接下来执行update时都在获取“Exclusive Lock”的同时等待对方释放“Shared Lock”,从而导致了死锁。

Mysql监测到死锁时,会回滚掉undo日志量最小的那个session的事务(持有最少行级排它锁的事务回滚

tips:共享锁“Shared Lock”,是读取操作创建的锁。所有请求可以并发读取数据,即给该优惠券row行上加了多把共享锁,此时任何事务都不能对该优惠券进行修改(就是获取该优惠券的排他锁),直到已释放所有的共享锁。因为MySQL默认事务隔离级别是可重复读(REPEATABLE READ),事务A读取到的优惠券信息,在事务A还未提交事务时,事务B不能把这个优惠券信息给改了!

解决方案:
1.尽量避免在同一个Transaction中先select再update(锁定同一条row),除非保证当前接口是串行调用的

2.MySQL单节点时,可以使用select ... for update加悲观锁。但是集群下,select...for update锁住的是每个节点的row,之后进行update无法保证死锁!
3.确实需要这么做的情况下,在select之前先做一个不产生任何效果的update,直接抢到“Exclusive Lock”,然后再执行后续操作

注意:

UPDATE t_coupon
        SET
        surplus_count = surplus_count - 1,
        draw_count = draw_count + 1,
        update_time = now()
        WHERE
        coupon_id = 31552;

这种update语句在事务中,高并发调用时。也会造成死锁!

因为surplus_count = surplus_count - 1, draw_count = draw_count + 1,update_time = now(),要先读出surplus_count、draw_count,now()

这种写法会先去拿该优惠券row的“Shared Lock”共享锁再去拿“Exclusive Lock”独占锁!!!

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值