商品库存的扣除过程,如何防止超卖?

本文探讨了商品购买过程中库存扣减的伪代码实现,包括串行化更新的并发问题和乐观锁策略的使用,以及直接扣减库存的潜在风险。重点介绍了如何利用Redis进行库存操作以提高并发性和一致性,以及不同方法的优缺点和适用场景。

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

在商品购买的过程中,库存的抵扣过程,一般操作如下:

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

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

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

上述过程的伪代码如下:

// 根据商品id获取商品剩余库存
select stock_remaing from stock_table where id=${goodsId};

// 操作库存
// 比较库存
if(stock_remaing <quantity){
   // 抛出库存不足的异常
}
else{
  // 抵扣以后的库存值
  int new_stock=stock_remaing - quantity;
}

// 根据商品id设置计算后的库存
update stock_table  set stock_remaing =${new_stock} id=${goodsId};

并发修改数据库存超卖

如果数据库事务的隔离级别不是串行化(serializable),根据事务的特性,在并发修改的时候,可能会出现写覆盖的问题。

假设,商品的剩余库存stock_remaing 为100,客户A下单20,客户B下单30,在并发扣库存的时候,可能存在超卖。如果客户A和客户B同时获取剩余库存为100,则会出现事务后提交的值会覆盖前一个客户提交的值,有可能剩余的库存是80或者70。

流程如下:

加锁更新存库

为了在事务控制中,防止写覆盖,你会想到使用select for update的方式,将该商品的库存锁住,然后执行余下的操作。

流程如下:

以上,使用悲观锁方式,在分布式服务中,如果并发情况比较高的时候,扣减库存的操作是串行操作,效率很低。

使用乐观锁的方式更新

在更新的时候,使用(CAS+版本号更新)+重试条件(重试次数或者重试时间限制)乐观锁的方式更新库存。此时,如果,客户A和客户B同时读取到库存剩余100,在更新的时候,有一个操作会失败。

流程如下:

该种方式可以大大提高并发性,也可以保证数据的一致性;通过重试次数和重试时间的条件控制,可以防止过多的重试带来的数据库压力。

可以使用直接递减的方式执行么?

在抵扣库存的时候,有的人提议不执行select,计算,set三段式的操作,直接扣减的方式,并且对于扣减到小于零的情况作了判断。伪代码如下:

update stock_table set remaing_stock=remaing_stock-${quantity} 
where id =商品id
and remaing_stock>${quantity};

在分布式服务调用中,因为网络异常,获取服务器异常,可能在微服务调用时,存在服务重试。例如,场景的网关超时,服务重试机制。此时,该种方式不满足幂等性,而存在多扣的情况。例如,同一用户扣减库存时,服务重试,极端情况下,该用户扣减库存操作执行多次,则就出现了商品超卖。

可以使用redis进行库存的抵扣么?

由于没有研究过redis源码,对于这种方式参考了大牛的回复,答案是可以使用redis的事务性扣减余额,但在CAS机制上比mysql没有优势,高性能是因为其内存存储的原因,带来的副作用是数据有丢失风险。

原文链接:https://blog.csdn.net/new_com/article/details/105568124

版权声明:本文为CSDN博主「iloveoverfly」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。

近期热文推荐:

1.1,000+ 道 Java面试题及答案整理(2021最新版)

2.劲爆!Java 协程要来了。。。

3.最新!Log4j 2.x 再发版,正式解决核弹级漏洞,又要熬夜了。。。

4.Spring Boot 2.6 正式发布,一大波新特性。。

5.《Java开发手册(嵩山版)》最新发布,速速下载!

觉得不错,别忘了随手点赞+转发哦!

### 使用 Lua 脚本防止电商商品 为了有效防止电商系统中的商品现象,可以通过 Redis 和 Lua 脚本来实现原子操作。这种方式能够确保库存更新过程不会受到并发请求的影响。 当多个客户端几乎同时尝试购买同一商品时,传统的读取-修改-写入模式可能会导致数据竞争条件,从而引发问题。通过将整个库存检查和扣减逻辑封装在一个 Lua 脚本内,并由 Redis 执行该脚本,可以在单个事务中完成这些操作,避免了中间状态被其他命令干扰的可能性[^2]。 下面是一个简单的 Lua 脚本示例,用于处理订单并减少相应数量的商品库存: ```lua -- keys[1] 是存储商品ID对应的剩余库存键名 -- arg[1] 表示要购买的数量 if (tonumber(redis.call('GET', KEYS[1])) >= tonumber(ARGV[1])) then redis.call('DECRBY', KEYS[1], ARGV[1]) return true; else return false; end ``` 此脚本首先获取指定商品当前的可用库存量并与所需购买的数量进行比较;如果现有库存充足,则扣除相应的数量并将结果返回给调用者表示成功下单;反之则拒绝此次交易请求以保护库存不被过度消耗。 在 Java 应用程序端,可以利用 `RedisTemplate` 来执行上述定义好的 Lua 脚本。这里需要注意的是传递给 `execute()` 方法的第一个参数应为已经加载到字符串变量里的完整 Lua 代码文本,而后续两个列表分别对应于 `KEYS[]` 数组(包含一个元素——商品 ID 对应的 key)以及 `ARGV[]` 数组(同样只有一个成员——欲购件数)。这样就能安全可靠地控制电商业务场景下的商品销售流程,预防可能出现的商品情况发生。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Java技术栈

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值