简介
本文件面向电商平台开发者,系统化梳理 DouPHP 电商系统的库存核心数据模型与计算逻辑。重点说明:
- 库存主表设计(以 product 表的 stock 字段为核心)
- 订单维度库存锁定字段 stock_lock 的作用
- 实时库存、在途库存、安全库存、预警库存的计算方法
- 并发场景下的库存锁定与释放机制
- 数据一致性保证、事务处理与性能优化建议
项目结构
围绕库存的核心代码主要分布在以下位置:
- 数据库定义:系统表结构 SQL 中定义了 product 与 order_item 等关键表
- 模型层:product 模块的后台/前台 Model 声明了 stock/sales 等字段的类型与行为
- 订单流程:购物车、结账、订单状态迁移中涉及库存检查、锁定与扣减
graph TB
A["产品表(dou_product)"] --> B["订单商品明细(dou_order_item)"]
C["购物车服务"] --> D["结账服务"]
D --> E["订单状态迁移"]
E --> A
E --> B
核心组件
- 产品表(dou_product)
- 关键字段:id、stock、sales、price、promote_price、status 等
- stock 表示当前可用库存;sales 表示累计销量
- 订单商品明细(dou_order_item)
- 关键字段:item_id、item_number、stock_lock、order_status 等
- stock_lock 用于标记该订单项是否占用库存
- 产品模型(Product)
- 声明 stock/sales 为整型,便于业务层进行数量运算
- 订单流程服务
- 购物车:下单前校验库存
- 结账:根据配置决定是否锁定库存
- 订单状态迁移:支付成功后扣减库存并累加销量
架构总览
库存相关的关键交互如下:
- 用户添加购物车时,调用库存检查接口,防止超卖
- 创建订单时,依据站点配置决定是否锁定库存
- 订单支付成功后,执行库存扣减与销量累加
- 订单取消或退款时,需释放已锁定的库存
sequenceDiagram
participant U as "用户"
participant C as "购物车服务"
participant O as "结账服务"
participant S as "订单状态迁移"
participant P as "产品表(stock)"
participant I as "订单商品明细(stock_lock)"
U->>C : 加入购物车(含数量)
C->>P : 检查库存(可购买性)
C-->>U : 返回结果
U->>O : 提交订单
O->>I : 写入订单项(设置stock_lock=1或0)
O-->>U : 返回订单号
U->>S : 支付成功回调
S->>P : 扣减库存(stock -= item_number)
S->>P : 累加销量(sales += item_number)
S-->>U : 返回支付完成
详细组件分析
库存主表设计(dou_product)
- 表名:dou_product
- 关键字段
- id:商品唯一标识
- stock:库存数量(整型),表示当前可用库存
- sales:累计销量(整型),用于统计与展示
- price/promote_price:价格与促销价,影响可售性与展示
- status:上架状态,仅上架商品参与销售
- 设计要点
- stock 作为“可用库存”的权威来源,所有扣减均基于此字段
- sales 与 stock 保持联动:每笔有效交易完成后,stock 减少、sales 增加
- 通过 casts 将 stock/sales 强制为整型,避免字符串运算导致精度问题
erDiagram
dou_product {
int id PK
smallint stock
mediumint sales
decimal price
decimal promote_price
tinyint status
}
订单维度库存锁定(dou_order_item.stock_lock)
- 表名:dou_order_item
- 关键字段
- item_id:关联商品ID
- item_number:购买数量
- stock_lock:是否锁定库存(tinyint,默认1表示锁定)
- order_status:订单状态,用于判断是否应释放锁定
- 作用
- 在订单创建阶段,若启用库存锁定,则对相应商品进行“预占”
- 当订单取消/过期/退款时,需释放对应数量的锁定库存
- 与 product.stock 配合,实现“可用库存 = 实际库存 - 锁定库存”的业务视图
flowchart TD
Start(["创建订单"]) --> CheckCfg{"是否启用库存锁定?"}
CheckCfg --> |是| Lock["写入stock_lock=1<br/>预占库存"]
CheckCfg --> |否| Skip["不锁定库存"]
Lock --> End(["等待支付"])
Skip --> End
库存计算逻辑
- 实时库存
- 定义:当前可用于销售的库存数量
- 计算:real_stock = product.stock - sum(order_item.item_number where stock_lock=1 and not paid/cancelled/refunded)
- 说明:未支付的锁定订单会占用可用库存;支付成功后转为实际扣减
- 在途库存
- 定义:已锁定但尚未完成支付的库存
- 计算:in_transit = sum(order_item.item_number where stock_lock=1 and order_status in pending/paid_not_shipped)
- 用途:用于库存看板与预警
- 安全库存
- 定义:为避免缺货而保留的最小库存阈值
- 计算:由运营策略决定,通常作为常量或配置项
- 用途:当 real_stock < safety_stock 时触发补货提醒
- 预警库存
- 定义:低于安全库存时的告警阈值
- 计算:alert_stock = safety_stock * 系数(如1.2)
- 用途:当 real_stock < alert_stock 时发出预警
flowchart TD
A["读取product.stock"] --> B["汇总锁定库存(未支付/未发货)"]
B --> C["计算real_stock = stock - 锁定库存"]
C --> D{"real_stock < safety_stock ?"}
D --> |是| E["触发补货提醒"]
D --> |否| F["正常销售"]
C --> G{"real_stock < alert_stock ?"}
G --> |是| H["发出预警通知"]
G --> |否| I["继续监控"]
库存锁定与释放机制(并发安全)
- 锁定时机
- 购物车阶段:检查库存是否足够,防止超卖
- 结账阶段:根据站点配置决定是否锁定库存(stock_lock=1)
- 释放时机
- 订单取消/过期:释放锁定库存(恢复 real_stock)
- 订单支付成功:从 product.stock 中扣减对应数量,并累加 sales
- 并发控制建议
- 使用数据库行级锁或乐观锁(version 字段)确保并发更新的一致性
- 在扣减库存时使用原子更新语句,避免竞态条件
- 对高频热点商品采用队列化异步扣减,降低数据库压力
sequenceDiagram
participant T as "线程A"
participant DB as "数据库"
participant P as "product.stock"
participant I as "order_item.stock_lock"
T->>DB : 开始事务
DB->>P : SELECT stock FOR UPDATE
DB->>I : 查询锁定库存
DB-->>T : 返回stock与锁定量
T->>DB : 计算real_stock并校验
T->>DB : 更新stock_lock或扣减stock
DB-->>T : 提交事务
数据一致性保证与事务处理
- 一致性原则
- 任何库存变更必须与订单状态变更在同一事务内完成
- 扣减库存与累加销量必须成对出现,避免不一致
- 事务边界
- 创建订单:写入 order_item 与 stock_lock
- 支付成功:扣减 product.stock 与增加 sales
- 取消/退款:释放 stock_lock 或回滚库存
- 幂等性
- 支付回调需具备幂等处理,避免重复扣减库存
- 审计与日志
- 记录库存变更日志,便于追踪与对账
flowchart TD
Start(["订单支付成功"]) --> Txn["开启事务"]
Txn --> Deduct["扣减product.stock"]
Deduct --> AddSales["累加product.sales"]
AddSales --> Commit["提交事务"]
Commit --> End(["完成"])
性能优化方案
- 索引优化
- 为 product.id、order_item.item_id、order_item.order_id 建立索引
- 为 order_item 的 stock_lock、order_status 建立复合索引,加速锁定库存汇总
- 读写分离
- 读多写少场景下,使用只读副本查询库存快照
- 缓存策略
- 对热点商品的 stock/sales 使用内存缓存(如 Redis),缩短读取路径
- 使用延迟双检模式,避免频繁落库
- 批处理与异步
- 批量扣减库存时采用分批提交,降低锁竞争
- 非关键路径(如统计报表)异步处理
依赖关系分析
- 产品表与订单商品明细的依赖
- order_item.item_id 引用 product.id
- stock_lock 与 product.stock 共同决定可用库存
- 服务层依赖
- 购物车服务依赖库存检查能力
- 结账服务依据配置决定是否锁定库存
- 订单状态迁移负责最终扣减与销量累加
graph LR
P["product.stock"] --> OI["order_item.stock_lock"]
CS["购物车服务"] --> CK["结账服务"]
CK --> OT["订单状态迁移"]
OT --> P
OT --> OI
性能考虑
- 高并发下单
- 使用数据库行级锁或分布式锁,避免超卖
- 对热点商品采用分片或队列化扣减
- 查询优化
- 聚合锁定库存时尽量使用索引覆盖扫描
- 避免全表扫描与复杂子查询
- 缓存与降级
- 对只读场景使用缓存,提升响应速度
- 在极端情况下允许短暂的数据不一致,事后补偿
故障排查指南
- 常见问题
- 库存为负:检查是否存在并发扣减未加锁或重复回调
- 锁定库存未释放:检查订单取消/过期流程是否正确释放 stock_lock
- 销量与库存不一致:核对支付成功后的扣减与累加是否在同一事务
- 定位步骤
- 查看订单状态与 stock_lock 字段,确认锁定状态
- 核对 product.stock 与 order_item 的数量变化
- 检查是否有重复支付回调导致的重复扣减
- 修复建议
- 引入幂等键与去重表,防止重复处理
- 增加库存变更审计日志,便于追溯
- 对异常数据进行对账与补偿脚本
结论
DouPHP 的库存核心以 product.stock 为权威数据源,结合 order_item.stock_lock 实现订单维度的库存锁定与释放。通过购物车检查、结账锁定、支付扣减的三段式流程,保障库存一致性与防超卖。建议在并发场景下采用行级锁/乐观锁与队列化异步处理,并结合索引、缓存与审计日志,构建高性能、可观测的库存体系。
附录
- 字段参考
- product.stock:可用库存
- product.sales:累计销量
- order_item.stock_lock:是否锁定库存
- order_item.item_number:购买数量
- 建议扩展
- 增加 inventory_log 表记录每次库存变更
- 引入 version 字段支持乐观锁
- 提供库存盘点与对账工具