BEFORE UPDATE触发器是MySQL 5.7拦截负库存的唯一数据库层手段,必须基于NEW.stock判断并拒绝负值;它仅作用于挂载表,多表库存需应用层统一管控与行级锁保障。

BEFORE UPDATE触发器必须检查NEW.stock
MySQL 5.7不支持CHECK约束(会解析但不执行),所以触发器是唯一能拦截负库存写入的数据库层手段。关键点在于:必须用BEFORE UPDATE,且判断依据只能是NEW.stock——即即将写入的值本身。
常见错误是写成IF OLD.stock - 10 ,这假设每次扣10,但触发器无法预知业务扣多少;扣减量可能来自订单表字段、JSON参数或应用层计算,根本不可靠。
-
NEW.stock是目标值,它已经由UPDATE语句算好,直接校验最准确 - 不要在触发器里重新做减法,那是应用层职责
- 如果UPDATE语句没显式赋值
stock字段,NEW.stock就等于OLD.stock,不会误判
不能在触发器里UPDATE同一张表
比如在products表上建触发器,又在里面写UPDATE products SET stock = ...,MySQL 5.7会直接报错:ERROR 1442: Can't update table 'products' in stored function/trigger because it is already used by statement。
这个限制很硬,没有绕过方式。实际场景中容易踩坑的是“订单确认扣库存”这类逻辑:
- 错误路径:在
orders表上建AFTER INSERT触发器 → 试图UPDATEproducts→ 报错 - 正确路径:把触发器建在
products或order_items上,让库存表自己响应变更 - 如果必须从订单侧驱动,改用应用层
SELECT ... FOR UPDATE+UPDATE组合,别塞进触发器
并发下BEFORE UPDATE仍需配合行锁
仅靠触发器不能解决并发超卖。两个事务同时执行UPDATE products SET stock = stock - 1 WHERE id = 123,都读到stock = 1,都算出NEW.stock = 0,都通过BEFORE UPDATE校验,最终写入0——没超卖,但漏掉了“库存不足应拒绝”的业务意图。
真正防超卖,得让UPDATE自带条件判断:
- 应用层SQL必须带
WHERE stock >= ?,例如UPDATE products SET stock = stock - 1 WHERE id = 123 AND stock >= 1 - 触发器只做兜底:万一某条UPDATE漏了WHERE条件,
NEW.stock为负时立刻拦住 - 返回
ROW_COUNT()为0时,应用层要明确提示“库存不足”,而不是静默失败
MySQL 5.7下触发器不是万能锁
触发器只守得住它挂载的那张表。如果库存分散在products、warehouse_stock、shop_inventory三张表,而你只在products上加了触发器,那warehouse_stock被单独扣成-5,触发器完全无感。
这种多表库存场景,触发器最多是最后一道防线,不是第一道。真正可靠的方案是:
- 应用层统一走库存服务,所有扣减请求先查各表汇总值
- 扣减操作必须用
SELECT ... FOR UPDATE锁定涉及的所有行(注意索引是否生效) - 避免在触发器里调用函数、查其他表或做复杂逻辑——5.7性能差,还容易死锁
触发器代码越短越好,只做IF NEW.stock 这一件事。其余逻辑下沉到应用或上移到更高版本MySQL的CHECK约束。


















