超卖是怎么发生的:库存扣减的几种做法

两个人同时下单最后一件商品,系统都提示成功。本文说明超卖的成因、下单扣减与支付扣减的取舍,以及预占超时释放的设计。

要点速览
  • 超卖的根源是查询与扣减分成两步,并发请求都在时间差里通过了检查。
  • 把判断与扣减合并为带条件的原子更新,多数超卖不需要引入锁或队列就能解决。
  • 下单即扣会被恶意下单锁死库存,支付才扣会出现付款时售罄,折中是预占加超时释放。
  • 超时释放与支付存在竞态,支付回调必须能处理库存已被释放的情况。
  • 多渠道销售时给各渠道分配额度,比共享全部库存更不容易超卖。

超卖的典型场景:库存还剩一件,两个用户几乎同时点下单。两个请求都先查询库存、都看到大于零、都执行扣减,最后库存变成负一,而两张订单都已经生成。

先查后改是根源

问题出在「查询」和「扣减」是两步。两步之间有时间差,并发请求都会在这个缝隙里通过检查。解决思路是把判断与扣减合并成一个原子操作 —— 例如在更新语句里带上库存大于等于所需数量的条件,根据影响行数判断是否成功。影响行数为零就是库存不足,不需要事先查询。

这是最基础也最有效的一条。很多超卖问题不需要引入分布式锁或队列,把这一步改对就解决了。

下单扣减还是支付扣减

  • 下单即扣:库存准确,不会超卖,但未支付的订单会占住库存,恶意下单可以把商品锁死;
  • 支付成功才扣:不占库存,但下单时显示有货、付款时提示售罄,体验较差且容易产生纠纷;
  • 折中做法是下单预占加超时释放:下单时预占库存,设定一个支付时限,到期未付自动释放。这是多数场景的实际选择。

超时释放的两个细节

一是释放与支付的竞态:用户在超时边界付款,系统正好执行释放,结果是钱收了库存没了。处理办法是释放时再次校验订单状态,并且支付回调里要能处理「库存已释放」的情况 —— 通常是重新尝试扣减,失败则自动退款并通知。

二是释放任务本身的可靠性。依赖单一定时任务时,任务挂掉会导致大量库存被长期占用。应当监控待释放订单的数量,异常增长时告警。

多渠道同步是另一类问题

同一批货同时在自建商城和若干平台上销售时,各渠道的库存不是实时同步的。常见做法是给每个渠道分配额度而不是共享全部库存,牺牲一点周转效率换取不超卖。真要共享,就必须接受同步延迟带来的风险并准备好缺货处理流程。

大促场景还要额外考虑:瞬时并发远高于日常,数据库行锁可能成为瓶颈。相关的架构取舍在电商网站 / 商城定制方案阶段就应当明确。大促场景的容量与锁策略属于架构决策,相关取舍见平台与系统开发。

常见问题

电商系统怎么避免超卖?

把库存判断与扣减合并成一个原子操作,例如在更新语句中带上库存足够的条件,依据影响行数判断是否成功,而不是先查询再扣减。多数超卖问题改对这一步即可解决,不必引入分布式锁。

库存应该在下单时扣还是支付后扣?

多数场景采用折中方案:下单时预占库存并设定支付时限,超时未付自动释放。下单即扣会被未支付订单甚至恶意下单锁死库存,支付后才扣则会出现下单显示有货、付款时售罄的情况。

用户在超时边界付款会怎样?

可能出现钱已收但库存已释放的情况。设计上释放前要再次校验订单状态,支付回调也要能处理库存已释放的分支——通常是重新尝试扣减,失败则自动发起退款并通知用户,而不是把订单留在异常状态。

参考资料

  1. WooCommerce Documentation · WooCommerce
  2. 支付宝开放平台文档 · 支付宝开放平台
# 库存# 并发# 电商系统# 订单
免费咨询

聊聊你的项目

留下联系方式,我们一个工作日内联系你。先沟通需求和现状,再给方案;报价当面沟通。

  • 一个工作日内回复
  • 需求沟通免费,不强推方案
  • 交付管理后台与文档,可自行维护
仅用于本次咨询联系,不会用于其他用途。