电商系统的库存冻结与解冻怎么设计?一次讲清预占库存、扣减时机、回滚与一致性

大家好,我是一名有 4 年工作经验的 Java 后端开发。
电商系统里,库存问题很多人会先想到扣减,但真正复杂的地方往往在“冻结”和“解冻”。
这篇文章我想系统聊一聊库存冻结与解冻到底应该怎么设计,什么时候冻结、什么时候真正扣减、什么时候解冻,以及一致性怎么保证。

🦅个人主页
🐼


一、为什么要做库存冻结

如果用户刚下单你就直接永久扣库存,会遇到很多问题:

  • 用户下单后不支付
  • 支付超时订单大量堆积
  • 售后取消后库存要回补

所以大多数电商系统不会在“提交订单”那一刻就把库存永久扣死,而是:

先冻结,再在支付成功或发货节点真正扣减。


二、库存里的三个概念要分清

建议至少拆成:

  • available_stock:可用库存
  • frozen_stock:冻结库存
  • sold_stock:已售库存

下单时通常是:

  • 可用库存减 1
  • 冻结库存加 1

支付成功后再:

  • 冻结库存减 1
  • 已售库存加 1

超时取消则:

  • 冻结库存减 1
  • 可用库存加 1

三、为什么不能只维护一个 stock 字段

如果只有一个库存字段,你很难说清:

  • 现在剩下的是可卖库存,还是已经被占用的库存?
  • 用户未支付占用了多少?
  • 哪部分库存可以释放?

所以库存冻结设计的核心不是“加减一个数字”,而是:

区分库存的生命周期。


四、推荐设计方案

我更建议这样做:

4.1 下单成功

  • 扣减 available_stock
  • 增加 frozen_stock

4.2 支付成功

  • 扣减 frozen_stock
  • 增加 sold_stock

4.3 订单超时取消

  • 扣减 frozen_stock
  • 回补 available_stock

4.4 售后取消 / 订单关闭

根据业务节点决定:

  • 是否回补可用库存
  • 是否记入售后库存流转

五、数据库设计示例

CREATE TABLE product_stock (
    product_id BIGINT PRIMARY KEY,
    available_stock INT NOT NULL,
    frozen_stock INT NOT NULL,
    sold_stock INT NOT NULL,
    updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
);

如果想做得更稳,还建议有一张库存流水表:

CREATE TABLE stock_flow (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    biz_type VARCHAR(32) NOT NULL,
    biz_id BIGINT NOT NULL,
    product_id BIGINT NOT NULL,
    change_type VARCHAR(32) NOT NULL,
    available_delta INT NOT NULL,
    frozen_delta INT NOT NULL,
    sold_delta INT NOT NULL,
    created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
);

这样后面好排查。


六、SQL 怎么写更安全

6.1 冻结库存

update product_stock
set available_stock = available_stock - #{count},
    frozen_stock = frozen_stock + #{count}
where product_id = #{productId}
  and available_stock >= #{count}

6.2 支付成功转已售

update product_stock
set frozen_stock = frozen_stock - #{count},
    sold_stock = sold_stock + #{count}
where product_id = #{productId}
  and frozen_stock >= #{count}

6.3 取消订单解冻

update product_stock
set frozen_stock = frozen_stock - #{count},
    available_stock = available_stock + #{count}
where product_id = #{productId}
  and frozen_stock >= #{count}

这种条件更新能避免脏数据直接写穿。


七、最容易踩的坑

7.1 冻结了库存,但没有超时释放

最后库存会一直被无效订单占着。

7.2 支付成功和超时取消并发处理不好

这时候一定要结合订单状态机一起设计,不能只看库存表。

7.3 只有库存表,没有库存流水

一旦线上库存不对,几乎很难追。

7.4 只管数据库,不管缓存库存

如果你还有 Redis 预库存,那数据库和缓存库存一定要有补偿和对账。


八、面试中怎么回答

如果面试官问你:

电商系统的库存冻结与解冻一般怎么做?

你可以这样回答:

第一,库存系统里我一般不会只维护一个 stock 字段,而是至少拆成可用库存、冻结库存和已售库存,因为这三者对应的是不同生命周期。

第二,用户下单时我通常会先冻结库存,也就是减少可用库存、增加冻结库存;支付成功后再把冻结库存转成已售库存;如果订单超时取消,再把冻结库存释放回可用库存。

第三,库存更新 SQL 会尽量使用条件更新,防止并发下写穿;同时会配合库存流水表,方便后续审计和排查。

第四,如果系统前面还有 Redis 预扣减,那数据库库存和 Redis 库存还要通过补偿和对账机制保证最终一致。


九、总结

库存冻结与解冻真正要解决的,不是“加回来还是减下去”,而是库存生命周期管理。

如果只记一句结论,我觉得可以记住这句:

电商库存最稳的设计通常不是直接永久扣减,而是“下单先冻结、支付后转已售、取消时再解冻”,并配合流水和状态机一起落地。


十、结尾

如果你觉得这篇文章对你有帮助,欢迎点赞、收藏、关注。
后面我会继续整理一些更偏实战的 Java 后端和电商系统设计文章。

Logo

电商企业物流数字化转型必备!快递鸟 API 接口,72 小时快速完成物流系统集成。全流程实战1V1指导,营造开放的API技术生态圈。

更多推荐