

新闻资讯
技术学院正确做法是UPDATE时在WHERE中加入库存校验条件,如WHERE id = 123 AND stock >= 1,并检查ROW_COUNT();复杂逻辑需配合SELECT FOR UPDATE加行锁,且WHERE条件避免函数导致索引失效。
并发扣减库存最直接的错误,就是只写 UPDATE product SET stock = stock - 1 WHERE id = 123。这会导致超卖:两个请求同时读到 stock=1,各自执行减 1,最终变成 -1。
正确做法是把库存是否充足判断直接塞进 WHERE 条件里,让 MySQL 在更新前原子性校验:
UPDATE product SET stock = stock - 1 WHERE id = 123 AND stock >= 1;
执行后检查 ROW_COUNT()(MySQL 返回影响行数):
当扣减逻辑不止一行 SQL(比如要先查价格、再扣库存、再写订单),单纯靠 WHERE 校验不够,必须加行锁防止并发读写冲突。
关键点:
START TRANSACTION 内执行SELECT ... FOR UPDATE
会锁定该行(即使没命中索引,可能升级为表锁)COMMIT 或 ROLLBACK),不是语句结束示例:
START TRANSACTION; SELECT stock FROM product WHERE id = 123 FOR UPDATE; -- 此时其他事务对 id=123 的 SELECT FOR UPDATE / UPDATE 会被阻塞 UPDATE product SET stock = stock - 1 WHERE id = 123 AND stock >= 1; -- 检查 ROW_COUNT(),失败则 ROLLBACK COMMIT;
如果写成 WHERE id = ? AND stock - 1 >= 0,MySQL 无法用上 stock 索引(哪怕有联合索引),可能触发全表扫描+锁表,极大降低并发能力。
应始终保持 WHERE 中的列是独立出现的:
WHERE id = 123 AND stock >= 1(能走 PRIMARY KEY + 范围条件)WHERE id = 123 AND stock - 1 >= 0(stock - 1 是表达式,索引失效)WHERE id = 123 AND stock > 0(虽然语义等价,但某些旧版本优化器可能不走索引,>= 1 更稳)有人想用 id % 4 把商品分散到不同行来缓解热点,但这会破坏业务语义(同一商品多行库存难聚合),且无法解决单商品高并发问题。
真正有效的分片思路是:
纯 MySQL 方案里,UPDATE ... WHERE ... AND stock >= N + 事务 + 索引覆盖,已经能扛住几千 QPS 的秒杀流量;再往上,就得考虑缓存、队列、分库分表了。