商城超卖问题的几种解决方案

本文探讨了电商中常见的超卖问题及其原因,主要源于并发操作和数据库默认的库存扣除方式。提出了三种解决方案:1) 使用Redis队列进行库存管理,但可能造成库存无法及时释放;2) 锁定最后库存结合乐观锁,适用于小流量场景,但在高并发时可能导致多数请求失败;3) 锁定库存无乐观锁,确保不出现超卖,更适用于高并发环境。每种方案都有其适用场景和优缺点。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、描述:

       商城设计的过程中,必然会考虑到一个库存扣除的问题,超卖将会带来一定的损失和麻烦,在某一个时间段的瞬间的流量会造成库存的并发性操作带来库存超额扣除。针对这类的问题,我先来分析原因吧。

二、为什么会被超卖

只有弄清楚为什么会被超卖才能解决超卖问题,这里我们要弄清楚mysql的默认的扣除方式,update 表 store=store-1 where id=指定的id
首先我们看这样的语句有哪些问题呢?

  1. 库存有可能扣到为负数,无法锁定最后的库存
  2. 如果从外面select一次库存,再查找又怕出现主从不一致问题
  3. 并发问题

如何解决以下问题呢?

三、解决方案

1、采用redis队列的方式进行库存增减运算。将创建订单、退款、取消订单做list的相应的增减操作

(1)创建商品、变更数量

  初始化redis的list结构,变更商品数量的时候,redis的list数量做相应的增减。

(2)商品上架、下架

 下架:设置redis的list过期

 上架:初始化redis的list

(3)创建订单成功

  list进行出队列的lpop

(4)取消订单、退款

  list的lpush入队列,进行补偿

(5)库存校验

 判断list的数量-当前的购买的数量 > 0的情况

 否则:提示库存不足

在这里插入图片描述

这种方案,看起来很完美,没啥问题,细心下你会发现,假如一些用户进入队列后,他不想买了,停在页面时间较长,这个就比较不好处理了,直接性导致很多刃余库存无法释放,估计还得写lua来控制了,比较得不偿失。

2、 锁定最后库存+乐观锁

系统中通过事务来判断,保证扣减后数据不为负数,否则回滚

设置数据库字段数据为无符号证书,通过扣减是,字段值小于零触发sql语句报错

select id,store,status,version from 商品表 where id=#1 and status=1,读出库存以及版本

修改库存:update 商品表 set store=store-#1,version=version+1 where status=1 and version=#version and store - #1 》 0

读取库存和版本信息的时候可强行读主库,避免主从不一致的情况,版本信息,

判断:库存-num > 0,版本=当前版本,避免延迟。

缺点:这种方案也是网上最多的方案,看起来很完美的,实际上也是有缺陷的,假如你的接口事务开始到事务结束,所需的时间是200ms,在这个200ms内的所有并发操作,只有一个人是成功的,其它用户因为乐观锁的原因都是失败的。当日了这个是针对并发量较大的情况才会发生的,一般的小流量倒是可以对付的。
在这里插入图片描述

3、 锁定最后库存无乐观锁

系统中通过事务来判断,保证扣减后数据不为负数,否则回滚
反正我不管最终update多少次,只要扣除成功了就行。也就是说我守住最后的底线,这种情况就不会出现一个人成功,其他人都失败的情况。事务过程中实际也是刷新脏页的过程,这种过程主库的update操作实际都是内部有个排队机制的,不会出现超卖的现象。
update 商品表 set store=store-#1 where status=1 and store - #1 》 0

个人认为3方案更加人性化一些,也比较简单,不需要读取过多的信息
1方案成本较高一些,当然了大型项目比较适合
2方案比较严谨一些,避免出现问题的几率较大,大型公司的mysql服务器在这个中间事务开始到操作结束,10ms不到,这种情况下一人成功,其他人失败问题不大。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值