库存改成 0 还能支付?一次缺陷误报背后的库存模型与事件驱动方案
📝 库存改成 0 还能支付?一次缺陷误报背后的库存模型与事件驱动方案
缘起:一张缺陷单
测试同学在缺陷平台提了单:待付款订单包含多个商品时,在后台把其中一个商品的库存改为 0,点「立即支付」,订单仍然正常支付成功——“没有检验住”。
第一直觉很容易站队测试:付款前连库存都不看,这不是裸奔吗?但把代码链路完整走一遍后,结论反转了两次——先证明这不是缺陷,再在边界实验里挖出一个真正的问题,最后收敛出一个跨系统方案。
图 1:现状全链路——下单占库存主线、ERP 改库存支线与状态卡死场景。
第一层:支付时到底校验了什么
「立即支付」的校验链路是:本人校验 → 待付款状态 → 过期懒取消 → 商品可购性校验(链接可见 + SKU 启用)。确实没有库存校验——这不是漏写,是模型决定的。
第二层:这套系统的库存模型是”拍下减库存”
| 时机 | 行为 |
|---|---|
| 下单 | 原子扣减可售库存(带 available_stock >= 购买数量 的 guard),写一条库存占用账(RESERVED) |
| 支付成功 | 占用账转为”已售”并累加销量,可售库存保持不变 |
| 取消 / 超时 | 归还可售库存,占用账释放 |
推演缺陷场景:商品库存 5 → 下单占 1 → 可售库存 = 4 → 后台改成 0 → 支付成功 → 占用转已售。全程没有超卖——那 1 件在下单那一刻就已经属于这张订单了,后台改的”可售库存”管的是下一个买家能不能下单。
为什么”支付时再校验库存”反而是 bug
假设支付时加上 可售库存 >= 购买数量 的校验:
- 误伤所有正常订单:已划走的份额会被重复计算。库存 1 的商品,用户拍下后可售库存变成 0,正常支付也会被拒。
- 制造数据矛盾:拦下来 → 订单超时取消 → 取消逻辑把 1 件归还回去 → 后台刚设置的 0 被顶回 1,运营的操作凭空失效。
“拍下减库存”正是淘宝、京东的业界主流模式;”付款减库存”是另一种模式,切换它需要产品拍板,不是改一行代码的事。给测试同学的回应也简单:想让这张待付款订单付不了款,正确操作是下架商品链接或停用 SKU——这两个状态支付链路会拦。
反转:边界实验真的发生了
有意思的是,当天下午测试恰好做了边界实验,把模型外的真实问题炸了出来:
后台把库存改成 0 → 商品被置成「售罄」→ 取消订单 → 归还逻辑把库存加了回来 → 库存变回 1,但售罄状态没人翻回去。
查 SIT 库实锤:测试商品 X 的状态是”售罄”(11:24:29 写入),可售库存却是 1,还有一张待付款订单的占用账挂着。有同事怀疑”售罄是 App 端改的”——把商城仓库翻了个底朝天,商品表只有三个写操作:库存扣减、库存归还、销量累加,从不碰任何状态字段。写售罄的是 ERP/后台侧,而库存回升后没有任何一方负责把状态翻回”在售”。
这次实验反而证明了拦截机制是好的:支付校验的是商品状态,不是库存数字——售罄状态落库后,支付确实会被拦。之前那张缺陷单能支付,只是因为售罄标记当时还没写进库。
真正的根因:商品状态与库存分属两个系统,状态被置售罄后库存回升,缺一条对账规则把状态翻回来。顺带一提,后台那次编辑是绝对覆盖库存为 0,完全无视在途的 2 件占用——这本身也值得后台改库存的功能感知占用账。
方案讨论:双写不行,事件驱动可以
ERP 侧最初提议:商城在归还库存时顺手把状态翻回”在售”(双写)。画时序图推演并发场景后,这个方案被否决:
图 2:双写方案的并发竞态反例与事件驱动方案。
双写方案的三个问题(图 2 场景一):
- 运营意图被覆盖:运营手动售罄后,商城一次取消订单的自动归还就把状态翻回”在售”——运营的动作被代码顶掉;
- 并发互相覆盖库存:运营读旧值 0 准备写入的同时,商城归还了 1 件,随后 ERP 基于旧值绝对写入 0——净丢 1 件,账实不符;
- 判定口径重复实现:链接级状态与 SKU 级库存的判定规则要在两个系统各写一遍,改一处漏一处。
事件驱动方案(图 2 场景二):商城只发布”库存已回升”事件(携带 SKU、归还数量、当前库存),不改状态;ERP 消费事件后按自己的规则判断——区分运营手动售罄与自动售罄——再决定是否执行状态变更。
收益:单一写权无竞态、不覆盖运营意图、判定口径只维护一份、状态变更可追溯;还顺带解决了”库存为 0 但状态是在售”的另一半不一致。
复盘四条
- 先澄清模型语义,再谈代码对错。“没校验库存”在拍下减库存模型里是正确行为——模型对齐之后,一半的争论会自动消失。
- **状态与库存分属两个系统时,写权必须单边持有。**双写即竞态,竞态即账实不符。
- 绝对覆盖写(set 0)必须感知在途占用,否则与占用账模型天然冲突。
- **对账不是补救,是分布式状态的标配。**两个系统各自持有真相时,”事件通知 + 单侧规则消费”是成本最低的和解方式。








