刚毕业那会,本人意外获得拼夕夕的面试机会,在聊到优惠券库存扣减场景时,面试官问了一连串经典的问题:“优惠券库存怎么扣减的?开发为了解决超发优惠券问题而设计的方案,了解过吗?你又是如何测试的呢?”。
当时听到这些问题还挺懵的,没遇到过超发问题啊?开发设计的方案我怎么知道?现在想起来还挺幼稚的,其实现在想想电商中有很多类似的问题,比如商品超卖,归根究底,就是一个问题,那就是并发安全问题。
问题引入
就拿领取优惠券的问题来说,
需求描述:A 优惠券一共发行 100张,每一个用户最多可以领取5张。
当一个用户领取优惠券成功的时候,把领取的记录写入另外一个表中(这张表我们暂且称为表 B)。
在领取优惠券的过程中,优惠券库存的扣减过程,一般操作如下:
1、select查询优惠券的库存。
2、计算优惠券库存是否足够,如果优惠券存库不足则抛出库存不足的异常,如果优惠券库存足够,则判断是否在领取时间、判断用户领取数量是否超过个人最高领取限制。
3、如果2成立,则减去扣除的库存得到最新的库存剩余值。
4、set设置最新的优惠券库存剩余值
伪代码如下:
扣减优惠券sql如下:
<!--减优惠券库存的SQL-->
<update id="reduceStock">
update coupon set stock = stock - 1 where id = #{coupon_id}
</update>
并发量比较低的时候,几乎看不出来有问题,可是当我们开启多线程,去请求这个抢优惠券的接口时,问题出现了,id为19的这个优惠券库存为负数。多发了一个,什么原因呢?
深入解读并发安全问题
为啥并发量高的时候会出现优惠券库存多发的问题呢?原因如下截图:
上图中出现问题的环节其实是判断优惠券库存那个步骤,重点来了:
高并发情况下,如果同时来了两个线程线程 A和线程 B(可以理解成是两个请求),比如先来的那个线程A请求通过了检查,这时线程 A 还没有扣减库存,这时经过一番操作,线程B也通过了这个检查优惠券是否可领取的方法,然后线程 A 和线程 B 依次扣减库存或者是同时扣减库存。所以就出现了刚刚数据库出