高并发下怎样优雅的保证扣减库存数据的正确性

最近写了一个功能,是大家常见的扣减库存,这里总结一下,如何 优雅的保证库存扣减的正确性

尝试思路

1.同步代码块synchronized ,lock
public synchronized void reduceCouponStock(long couponId ,Integer num) {
//业务逻辑
}
​问题:synchronized 作用范围是单个jvm实例, 如果做了集群分布式等,就失效了,且单机JVM加锁后就是串行等待问题
2.分布式锁 zookeeper,redis 
可以解决问题
​问题:在这块使用不够优雅 过于笨重,性能有所下降

 

----------------------------正解-----------------------------我是华丽分隔符------------------------------------------正解------------------------------------------

如果扣减最多1个,则直接使用这种就行

update coupon set stock=stock-1 where id = #{couponId} and stock>0

解释场景:id是主键索引的前提下,如果每次只是减少1个库存,则可以采用上面的方式,只做数据安全
校验,可以有效减库存,性能更高,避免大量无用sql,只要有库存就也可以操作成功.
场景:高并发场景下的取号器,优惠券发放扣减库存等

 

 

如果扣减多个,用下面这种

update coupon set stock=stock - #{num} where id = #{couponId} and (stock - #{num})>0
或者
update coupon set stock=stock - #{num} where id = #{couponId} and stock >= #{num}

解释场景:

使用业务自身的条件做为乐观锁,但是存在ABA问题,对比方案三的好处是不用增加version版本字段。
如果只是扣减库存且不在意ABA问题时,则可以采用上面的方式,但业务性能相对方案一就差了点,因为库存变动后sql就会无效

 

问题又来了,扣减库存,如果别人补充库存,就存在ABA问题,怎么优雅的解决?

比如:

C线程查出来是10个
A线程扣减1个,剩9个
B线程更新了库存,变回10个
C更新的时候发现还是10个,则更新成功, 所以避免这个问题,要求不管谁修改了库存,一定要加个version递增版本号
​update coupon set stock=stock-1,version=version+1 where id = #{couponId} and stock>0 and versoin=#{oldVersion原先查询的版本号}

​解释场景:

增加版本号主要是为了解决ABA问题,数据读取后,更新前数据被别人篡改过,version只能做递增
场景:商品秒杀、优惠券方法,需要记录库存操作前后的业务

Guess you like

Origin blog.csdn.net/wnn654321/article/details/114992680