在orders表上定义UPDATE触发器直接修改自身会报错,正确做法是在order_items表建AFTER UPDATE触发器,根据status变化扣减或回补inventory.stock,且需用WHERE限定product_id、避免超卖需应用层加锁或条件更新。

UPDATE触发器里不能直接修改被更新的表
这是最常踩的坑:在 orders 表上定义 BEFORE UPDATE 触发器,试图更新同一张表的 status 字段时再去改 inventory.stock,MySQL 会报错 Can't update table 'orders' in stored function/trigger because it is already used by statement which invoked this stored function/trigger。触发器里操作的目标表不能是当前正在被 UPDATE 的那张表本身。
正确做法是让触发器只作用于库存表(inventory)或订单状态变更后影响库存的另一张关联表(如 order_items),且确保触发逻辑不反向触碰原表。
- 如果订单状态从
'pending'变为'confirmed',应在order_items表上建AFTER UPDATE触发器,读取变化行的quantity并扣减inventory.stock - 避免在
orders表上写触发器去“顺手”更新inventory——它没权限也没必要知道库存细节 - 触发器中禁止出现对当前表的
UPDATE、INSERT或DELETE语句
用AFTER UPDATE触发器安全扣减库存
典型场景是订单确认后减少商品库存。关键在于:必须用 AFTER UPDATE(不是 BEFORE),且只操作库存表,不碰订单主表。
假设 order_items 表有字段 order_id、product_id、quantity、status(值为 'pending' / 'confirmed'),库存存在 inventory 表的 stock 字段:
CREATE TRIGGER tr_order_item_confirmed
AFTER UPDATE ON order_items
FOR EACH ROW
BEGIN
IF OLD.status = 'pending' AND NEW.status = 'confirmed' THEN
UPDATE inventory
SET stock = stock - NEW.quantity
WHERE product_id = NEW.product_id;
END IF;
END;-
OLD.status和NEW.status是唯一可靠的判断依据,不能依赖NEW.updated_at时间戳做状态推断 - 必须加
WHERE product_id = NEW.product_id,否则可能误更新所有库存行 - 没有事务包裹?别急——触发器天然运行在父语句事务内,只要外部
UPDATE order_items成功提交,库存扣减就一起生效
库存回滚要靠反向UPDATE触发器
订单取消(status 从 'confirmed' 变回 'canceled')时,得把扣掉的库存加回去。不能复用同一个触发器混写逻辑,容易漏判或多执行。
推荐单独建一个触发器处理“回滚路径”,保持职责清晰:
CREATE TRIGGER tr_order_item_canceled
AFTER UPDATE ON order_items
FOR EACH ROW
BEGIN
IF OLD.status = 'confirmed' AND NEW.status = 'canceled' THEN
UPDATE inventory
SET stock = stock + OLD.quantity
WHERE product_id = NEW.product_id;
END IF;
END;- 这里用的是
OLD.quantity,因为原始扣减量在更新前就确定了,不能用NEW.quantity(它可能被改过) - 两个触发器可以共存,MySQL 按定义顺序执行,但彼此独立,互不干扰
- 如果业务允许“部分取消”,需额外校验
NEW.quantity ,再算差值补回
触发器无法替代应用层校验和并发控制
触发器能自动同步字段,但它不阻止超卖。比如两个并发请求同时把库存从 1 扣成 0,第二个请求仍会成功执行 UPDATE inventory SET stock = 0 - 1,结果变成 -1。
- 真正防超卖得靠
SELECT ... FOR UPDATE加行锁,或在 UPDATE 语句里加条件:UPDATE inventory SET stock = stock - 1 WHERE product_id = 123 AND stock >= 1 - 触发器里不能用
SELECT ... FOR UPDATE,会报错;也不能抛出自定义异常(MySQL 5.7+ 支持SIGNAL,但会中断整个事务,体验差) - 如果库存字段参与高频更新,触发器会拖慢主表写入性能——每行变更都多一次额外 UPDATE,建议压测验证 QPS 影响
触发器只是数据同步的胶水,不是业务规则的守门员。库存是否足够、能否下单、要不要告警,这些决策必须留在应用代码里做。


















