工作中的一个功能,又遇到可能会发生数据库死锁的风险

/ 后端 / 没有评论 / 361浏览

功能的需求:

增加对商品订单的修改功能;也就是可以对订单中的商品进行更换及数量的修改;

我的预期编写方案是:

由于商品在下单中,进行了很多其他的数据库修改行为,例如扣除库存,统计,会员积分等等;所以我预想的是如下操作:

1.对订单进行整体回滚操作,例如删除统计,回滚库存,回滚会员积分余额等;

2.使用之前下单分离出的各个处理方法,重新对该订单下单,进行一系列操作;

于是,我意识到了一个问题,就是如此大的数据库修改操作,目前由于体量问题,又是在一个事务中完成,也就是有一个数据库长事务的操作,务必会引发死锁;


死锁发生的地方,只举出一个比较明显会出问题的地方,就是在订单修改和下单同时并发进行的时候;举例说明:

现有商品四种分别是:

A商品
B商品
C商品
D商品

下面假设发生并发行为:

管理员修改订单客户端下单
对C.D商品进行库存回滚修改

下单A.C商品 (此时由于修改订单原因,触发C商品的排他锁,客户端事务等待状态)
由于修改后,对A.B商品重新下单(此时由于客户端A商品占有锁,并且无法释放,导致客户端发生死锁,订单修改成功)



同时如果加入了会员信息回滚,如果在同一事务中,一样也会发生死锁:

管理员修改订单客户端下单
回滚A会员余额;扣减A商品库存,

并且扣减A会员余额; (此时处于等待状态)
回滚A商品库存;(此时发生死锁)


原因:

其实也看出来了,就是一个资源循环占用锁,互相无法释放的原因导致的;

从上面可以看出,用最基本的方式对商品进行排序,使发生数据修改的资源有序化已经无法避免死锁;如果只是下单扣库存则可以避免死锁;但是像修改订单这个长事务,已经改变了有序性;


解决方案:

1.如果并发量不高,并且可以容忍死锁造成的一次单方失败,则可以保持不动;(mysql会认为回滚事务代价最小的一方回滚)

2.优化代码,减少事务的处理时间,降低并发;

3.如果可以将两次修改行为分离出来,进行有序化;例如将回滚商品和扣除商品分离出来,进行排序合并后一并更新;

4.对同一事物中,所有资源进行有序化修改;

5.对功能进行重构,例如使用消息等异步最终一致性方式,减少长事务的存在;