团购网站设计:最后一份库存怎样防止超卖

先查库存大于零再扣减,在并发请求下仍可能卖出超过库存的份数。

edyang · 2014-09-15

先查库存大于零再扣减,在并发请求下仍可能卖出超过库存的份数。判断和修改要落在同一约束中。

本文使用团购业务的虚构订单与关系数据模型,伪代码不连接真实支付。团购把用户购买、平台收款、商家履约与结算连接起来。下单、支付、成团、消费和退款必须保留各自状态,不能用一个布尔字段表达整个生命周期。

1. 从现象推到机制

把库存条件放进数据库更新语句,利用行更新的原子性决定是否成功。受影响行数为零时表示条件不满足,不能继续创建成功预占。库存预占、订单创建与释放需要有明确事务和到期规则。

团购网站设计:最后一份库存怎样防止超卖的处理、反馈与观察关系

图中的四个节点分别表示购买请求、条件扣减、预占记录、释放迁移。顺着实线检查输入如何变成输出,再沿虚线核对反馈条件。图示表达的是本文讨论的关系,不是实测曲线或现场截图。

2. 最小实现与输入条件

示例一次预占一份,需在实际数据库支持的事务内执行。释放预占也应通过预占记录状态控制,避免超时任务重复返还。

1
2
3
4
UPDATE offer_stock
SET available = available - 1
WHERE offer_id = :id AND available >= 1;
-- affected_rows = 1 才表示预占成功

先列业务事件与允许的状态迁移,再用少量匿名订单演算。涉及金额的地方保存成交快照,涉及并发的地方使用数据库约束,外部支付行为按支付协议另行实现。

3. 用对照区分原因

下表列出需要主动改变的条件。预期行为来自机制分析,复现时还需记录运行环境和原始输出,不能将预期当作已经测得的结果。

改变的条件 需要验证的行为
两个请求争最后一份 只有一个条件更新成功
预占后支付超时 到期后按规则释放一次
释放任务重复运行 不能多返还库存

对照 1:两个请求争最后一份

只有一个条件更新成功。观察时记录受影响行数与最终可用库存。把这个条件与基准情形分开,有助于判断变化来自哪一层;若得到相反结果,应先核对前提,再修改实现。

对照 2:预占后支付超时

到期后按规则释放一次。观察时记录预占状态与释放事件身份。把这个条件与基准情形分开,有助于判断变化来自哪一层;若得到相反结果,应先核对前提,再修改实现。

对照 3:释放任务重复运行

不能多返还库存。观察时记录状态迁移是否具备条件限制。把这个条件与基准情形分开,有助于判断变化来自哪一层;若得到相反结果,应先核对前提,再修改实现。

4. 适用条件与取舍

数据库扣减正确不代表整体订单流程正确。订单失败后的补偿、退款是否返库存,以及库存代表券数还是预约时段,都要单独约定。

一次正常运行只覆盖了这组输入。继续扩展前,应把上面的异常对照保留下来,尤其关注“释放任务重复运行”这一分支。后续方案若改变了图中任意一处依赖,原来的判断就要重新验证,不能仅凭界面相似或返回值相同沿用结论。

同月延伸

参考资料

  1. Spring 3.2.6 事务管理
  2. Hibernate 4.3 事务与并发