redis队列扣库存不能仅lpush+brpop,因入队前未校验库存会导致超卖;须先get校验、入队完整指令、消费者二次原子校验;mysql扣减需where stock>=#{buycount}并检查affectedrows;redis+lua须将读判改封装为原子脚本;须建立定期对账机制防redis与db不一致。

Redis 队列扣库存:为什么不能直接 LPUSH + BRPOP 就完事
并发超卖的根本原因是多个请求同时读到“还有库存”,然后都执行了扣减。用 Redis 队列本质是把“扣减”从并行变成串行,但队列本身不解决“库存是否充足”的判断问题。
常见错误是:用户下单后只往队列塞一个订单 ID(LPUSH order_queue 1001),后台消费者再从队列取、查 DB 库存、扣减——这中间查库存和扣减不是原子的,两个消费者可能同时查到“剩余 1”,都扣成 -1。
- 必须在入队前就做一次快速库存校验(比如用
GET stock:goods_123),失败直接拒绝,不入队 - 入队内容建议是完整扣减指令,例如 JSON:
{"goods_id":123,"sku_id":456,"quantity":1,"order_id":"ord_abc"},避免消费者再查业务参数 - 消费者扣减时,仍需用数据库乐观锁或 Redis 原子操作二次校验,不能无脑执行
-
BRPOP要设超时(如BRPOP order_queue 30),否则消费者挂掉会导致队列积压且无感知
MySQL 乐观锁扣库存:version 字段和 stock 条件更新哪个更靠谱
两者都能用,但场景不同。version 适合业务逻辑复杂、涉及多表更新的订单;纯库存扣减,直接比对 stock >= #{buyCount} 更简洁、少一次查询、不易错。
典型错误写法:UPDATE goods SET stock = stock - 1 WHERE id = 123 AND stock > 0——看起来安全,但没校验要扣的数量,买 10 件时只扣了 1。
- 正确条件必须包含数量:
UPDATE goods SET stock = stock - #{buyCount} WHERE id = #{id} AND stock >= #{buyCount} - 执行后必须检查
affectedRows == 1,为 0 说明库存不足,不是异常,是正常业务拒绝 - 不要在同事务里先
SELECT stock再UPDATE,这是典型的“读-改-写”竞态,即使加了事务隔离也没用 - 如果用
version,记得每次更新都SET version = version + 1,且 WHERE 条件带上AND version = #{oldVersion}
Redis + Lua 原子扣减:为什么非得写 Lua,不能用几个 DECR 拼起来
因为单个 DECR 或 GETSET 只能保证一个 key 的原子性。库存校验 + 扣减是两个动作:先读当前值,再决定是否扣。分开调用,中间就可能被其他客户端插队。
错误做法:先 GET stock:123,应用层判断够不够,再发 DECRBY stock:123 1——两次网络往返之间,库存可能已被别人扣走。
- 必须用 Lua 把“读-判-改”包在一个脚本里,由 Redis 保证原子执行
- 示例脚本:
EVAL "if redis.call('GET', KEYS[1]) >= ARGV[1] then return redis.call('DECRBY', KEYS[1], ARGV[1]) else return -1 end" 1 stock:123 1 - 返回 -1 表示库存不足,应用层据此返回错误,而不是抛异常
- Lua 脚本里不能调用带随机性或时间相关的命令(如
TIME),也不能有循环或长耗时逻辑,否则会阻塞 Redis 主线程
实际部署时最容易被忽略的点:Redis 和 DB 库存不一致怎么发现和修复
没人能保证 100% 不出错。消息丢失、Lua 脚本异常退出、DB 扣减成功但 Redis 没同步……这些都会导致两边库存数字对不上。等用户投诉“明明下单成功却发货失败”,已经晚了。
- 必须建立定期对账任务,比如每 5 分钟扫描最近 1 小时内有扣减操作的
goods_id,比对 RedisGET stock:xxx和 DBSELECT stock FROM goods WHERE id = xxx - 对账发现差异,不要自动修复。先记录日志(含时间、key、redis 值、db 值、操作流水号),人工确认后再决定回滚还是补偿
- Redis 库存建议设过期时间(如
EXPIRE stock:123 86400),避免脏数据长期残留;DB 库存永远以主库为准,Redis 只是加速层 - 上线前用
ab或wrk模拟 500+ 并发抢购,重点观察对账任务能否及时捕获不一致
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!










