库存扣减必须用 UPDATE ... WHERE stock >= ?
加条件判断
直接
UPDATE goods SET stock = stock - 1 WHERE id = 123是危险的——并发下单时可能超卖。正确做法是在 SQL 层就拦截非法扣减:
UPDATE goods SET stock = stock - 1 WHERE id = 123 AND stock >= 1;执行后检查
ROW_COUNT()返回值:为 0 表示库存不足或商品不存在,不是靠应用层查一遍再扣,那会引入竞态。
goods
表至少要包含 version
字段做乐观锁
仅靠
WHERE stock >= 1还不够,比如两次请求同时读到
stock=5,都通过校验,最终变成
3而非
4(应只允许一次成功)。加
version字段可解决:
UPDATE goods SET stock = stock - 1, version = version + 1 WHERE id = 123 AND stock >= 1 AND version = 5;应用需先查出当前
version,更新时带上,失败则重试。不依赖数据库行锁,更适合高并发读多写少场景。
订单生成和库存扣减必须在同一个事务里完成
常见错误是先插入订单,再异步扣库存——一旦扣减失败,订单已落库,状态不一致。正确流程是:
开启事务 执行带AND stock >= ?和
version校验的
UPDATE检查影响行数是否为 1 插入订单记录(含商品 ID、数量、快照库存值) 提交事务 任何一步失败都回滚,保证原子性。注意:MySQL 必须用
InnoDB引擎,
autocommit=0或显式
BEGIN。
历史库存变动要用独立表记录,别只靠 goods.stock
反推
goods表的
stock是当前值,无法追溯“谁、何时、因何扣了 2 件”。必须建
stock_log表:
CREATE TABLE stock_log ( id BIGINT PRIMARY KEY AUTO_INCREMENT, goods_id INT NOT NULL, change_amount INT NOT NULL, order_id BIGINT, remark VARCHAR(100), created_at DATETIME DEFAULT CURRENT_TIMESTAMP );每次扣减/补货都写一条日志。排查超卖、对账、运营分析全靠它——别指望从订单表反向计算,退货、取消、补偿等逻辑会让反推变得不可靠。
