UPDATE product SET stock = stock - 1
WHERE id = 10086 AND stock >= 1;
SELECT stock FROM product WHERE id = 10086;

秒杀压测 20 个并发打同一件库存为 1 的商品,全部返回下单成功,最后查 stock 是 -19,库存流水里 20 条扣减记录都写进去了。这类现象通常不是突然出现的,往前翻半天日志能看到苗头。

追下去是这么回事:扣库存写的是 stock = stock - 1,没有把 stock >= 1 放进 where 条件,也没有加行锁。并发请求读到的都是 1,判断通过后各自更新,更新本身不报错。业务层把影响行数丢掉了,扣成负数也照常提交订单。

这次卡住的地方就在「库存负数校验」这一环,单独把它拎出来验证,别和其他改动混在同一次发布里。权限不要一口气开到最大,先加到够用再往上试,范围小才好收。

把复现步骤压到三条以内,交给同事也能跑出来,才算真的定位清楚。

处理上按这个顺序:把扣减改成 update set stock = stock - 1 where id = ? and stock >= 1;取返回的 affected rows 等于 0 时抛库存不足并回滚整个事务;高并发场景在 Redis 预扣或加分布式锁 减少数据库争抢;用 20 并发再压一次 核对 stock 和订单数。

确认没问题再收工,并发跑完 stock 停在 0 只有 1 条订单成功 affected rows 为 0 的请求返回业务错误码。

作者 9823189