库存扣减逻辑的核心是保证数据一致性,避免超卖。MySQL 实现的关键不在于“怎么写 SQL”,而在于“怎么设计表 + 怎么控制并发”。下面从表结构设计、扣减逻辑实现、常见陷阱三方面说清楚。
库存表基础字段设计(含关键约束)
一张精简但健壮的库存表至少包含以下字段:
id:主键,自增或业务 ID sku_id:商品唯一标识(建议加唯一索引) stock:当前可用库存(必须为 UNSIGNED INT,防止负数) lock_stock:已预占库存(用于下单锁定,非必需但推荐) version:乐观锁版本号(INT,默认 0,每次更新 +1) updated_at:最后更新时间(便于排查和监控)重要约束:stock 字段设为 UNSIGNED,配合 WHERE stock >= #{need} 条件,可天然拦截负库存更新;同时在事务中用 SELECT ... FOR UPDATE 或 UPDATE ... WHERE stock >= #{need} 做原子校验。
安全扣减的两种主流实现方式
不推荐直接 SELECT 后 UPDATE(存在竞态),应采用原子操作:
方案一:UPDATE + WHERE 校验(推荐初/中级场景)UPDATE inventory SET stock = stock - #{num}, updated_at = NOW() WHERE sku_id = #{skuId} AND stock >= #{num};执行后检查 影响行数是否为 1。为 0 表示库存不足或已被扣完,业务层直接拒绝下单。 方案二:SELECT FOR UPDATE + UPDATE(适合高一致性要求)
在事务中先查再锁:
SELECT stock FROM inventory WHERE sku_id = #{skuId} FOR UPDATE;检查 stock ≥ num,再执行 UPDATE。注意:该语句必须走索引(如 sku_id 有索引),否则会锁全表。
防超卖进阶:引入预占(lock_stock)与状态协同
秒杀或分布式下单场景下,仅靠最终扣减易导致“下单成功但支付失败却占着库存”。可增加预占机制:
用户下单时,先扣减 lock_stock += num,并校验 stock - lock_stock >= num 支付成功:真正扣减 stock -= num,并 lock_stock -= num 支付失败或超时:异步任务回滚 lock_stock -= num此时库存可用量 = stock - lock_stock,前端展示和扣减校验都基于该值,兼顾实时性与准确性。
