背景

在电商系统中买商品过程,先加入购物车,然后选中商品,点击结算,即会进入待支付状态,后续支付。 过程需要检验库存是否足够,保证库存不被超卖。

目的

  • 防止相同用户重复下单
  • 检查库存准确数量
  • 防止扣错库存数量
  • 扣库存时性能效率提升、不阻塞用户

一般过程

1.select根据商品id查询商品的库存。

2.根据下单的数量,计算库存是否足够,如果存库不足则抛出库存不足的异常,如果库存足够,则减去扣除的库存得到最新的库存剩余值。

3.set设置最新的库存剩余值。

显然,在多线程的情况下,可能会出现卖出商品的数量大于库存商品的数量,也就是电商中常说的 库存超卖。

解决方法

数据库

悲观锁

使用select···for update来将商品的库存锁住来避免库存超卖。

由于for update 会锁住所有扫描的数据,其他事务必须等这个事务执行结束再进行执行,所以性能比价低下

乐观锁

其实和java中常说的cas类似,比较然后替换,一般是在数据库中加一个version字段,每次修改的时候 看看自己拿到的版本号和数据中一样的不一样,一样就继续玩,不一样就放弃这个锁。

redis

使用redis原子操作+sql乐观锁

利用Redis decr的原子操作,保证库存数安全 先查询redis中是否有库存信息,如果没有就去数据库查,这样就可以减少访问数据库的次数。

  • 获取到后把数值填入redis,以商品id为key,数量为value。
  • 注意要设置序列化方式为StringRedisSerializer,不然不能把value做加减操作。
  • 还需要设置redis对应这个key的超时时间,以防所有商品库存数据都在redis中。 update使用乐观锁
update Product set count = count - #{购买数量} where id = #{id} and count - #{购买数量} >= 0;

虽然redis已经防止了超卖,但是数据库层面,为了也要防止超卖,以防redis崩溃时无法使用或者不需要redis处理时,则用乐观锁,因为不一定全部商品都用redis。

LUA脚本保持库存原子性

分布式锁

参考