秒杀库存扣减首选UPDATE WHERE原子操作,通过stock>=1条件防超卖;辅以订单表唯一索引防重复下单;复杂场景慎用SELECT FOR UPDATE显式事务;需分层防护+实时监控。

秒杀场景下库存扣减必须保证原子性、一致性、高性能,核心在于避免超卖,同时扛住瞬时高并发。不能靠应用层加锁或简单 select + update 实现,必须借助数据库原生机制与合理设计。
用 UPDATE WHERE 原子扣减(推荐首选)
直接在 SQL 中完成“读取库存并扣减”两个动作,由数据库保证原子性:
- 写法示例:UPDATE item SET stock = stock - 1 WHERE id = ? AND stock >= 1
- 执行后检查 影响行数:若为 0,说明库存不足或已被抢完;若为 1,则扣减成功
- 无需额外查库存、无需显式事务包裹(单条 UPDATE 默认是事务),减少网络往返和锁持有时间
- 注意:WHERE 条件中必须包含 stock >= 1,否则可能扣成负数
配合唯一约束防重复下单
仅控库存还不够,用户可能重复提交订单。需在订单表加唯一索引拦截:
- 在订单表建联合唯一索引:UNIQUE KEY `uk_user_item` (user_id, item_id, sku_id)
- 下单时插入订单记录,若违反唯一约束则报错(如 MySQL 的 1062 错误),应用层捕获后返回“已下单”提示
- 该方式轻量、高效,比应用层分布式锁更可靠,且天然幂等
必要时引入行锁 + 显式事务(慎用)
当业务逻辑复杂(如需校验优惠券、积分、多商品组合库存)时,可升级为显式事务,但必须严格控制范围:
- 开启事务后,先 SELECT ... FOR UPDATE 查询并锁定库存行(务必走主键或唯一索引,避免锁表)
- 再判断库存是否充足,充足则执行 UPDATE 扣减,否则 rollback
- 事务粒度要小:只锁库存行,不查无关表,不调外部接口,不 sleep,不输出日志到事务内
- 注意:InnoDB 下 FOR UPDATE 在 RC 隔离级别下是当前读,会加 next-key lock,避免幻读干扰
补充建议:分层防护 + 监控兜底
单靠 DB 不够,需构建多层防线:
- 前置限流:Nginx / 网关层按用户/IP/接口限流,过滤无效流量
- 缓存预热 & 校验:Redis 缓存库存(用 Lua 脚本保证扣减原子性),但最终以 DB 为准,缓存仅作快速拒绝(如库存 ≤ 0 直接返回)
- 异步核对:扣减成功后发 MQ,下游服务比对 DB 库存与订单数量,发现负数立即告警并熔断
- 监控关键指标:UPDATE 影响行为 0 的比例、死锁次数、慢查询中含 FOR UPDATE 的语句、Redis decr 返回 -1 的频次










