BEFORE触发器可修改NEW行并中止操作,AFTER触发器只读且无法阻止主DML;前者用于默认值、校验等前置控制,后者适用于日志、统计等后置动作。

BEFORE 触发器能改数据,AFTER 不能
这是最核心的区别:在 BEFORE INSERT 或 BEFORE UPDATE 中,你可以安全地修改 NEW 行的字段值,这些修改会真正写入表;而 AFTER 触发器里,NEW 是只读的——你试图赋值会报错:ERROR 1362 (HY000): Updating of NEW row is not allowed in after trigger。
-
BEFORE中可写:SET NEW.created_at = NOW(), NEW.email = LOWER(NEW.email); -
AFTER中写同句 → 直接报错 -
DELETE没有NEW,只有OLD;INSERT没有OLD,只有NEW;UPDATE两者都有
执行顺序决定能不能“拦住非法操作”
MySQL 的执行链条是严格线性的:BEFORE → 实际 DML → AFTER。这意味着:
-
BEFORE在语句执行前介入,可以用IF+SIGNAL主动中止整个操作(比如年龄为负就拒绝插入) -
AFTER永远在成功提交后才运行,哪怕你想“回滚”,也已经晚了——它无法阻止主操作发生 - 典型翻车场景:
AFTER INSERT更新库存,但订单插入时库存已不足 → 爆库;换成BEFORE INSERT先查库存再决定是否放行,才能守住一致性
什么时候必须用 BEFORE,什么时候只能用 AFTER
选错时机不是语法错误,而是逻辑漏洞。关键看你要做什么:
- 必须用
BEFORE:
– 自动填充默认值(如id、created_at)
– 格式化输入(转小写、去空格、脱敏)
– 数据校验并中断非法写入 - 只能用
AFTER:
– 写审计日志(要确保主操作已落地)
– 更新统计表(如销量+1,需确认订单真实插入)
– 调用外部系统(如发消息),不能因下游失败拖垮主事务
DELIMITER $$
CREATE TRIGGER order_before_check
BEFORE INSERT ON orders
FOR EACH ROW
BEGIN
IF NEW.quantity > (SELECT stock FROM products WHERE id = NEW.product_id) THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'Insufficient stock';
END IF;
END$$
DELIMITER ;
别忽略事务边界和性能影响
触发器天然运行在主 DML 所在事务中,这点极易被忽视:
-
BEFORE和AFTER都受同一事务控制:主语句回滚,触发器所有操作也一并回滚 - 但
AFTER里做耗时操作(如远程 HTTP 请求、大表 JOIN)会拖慢主事务,增加锁持有时间 - 所有触发器都禁止调用存储函数以外的非确定性函数(如
NOW()可用,UUID()在某些版本受限) - MySQL 8.0+ 支持多触发器同类型排序(
FOLLOWS/PRECEDES),但多数业务应避免依赖顺序,尽量单触发器闭环
真正难的从来不是语法,而是想清楚:这一步动作,是该“卡在门口检查”,还是“等进门后再记一笔”。顺序错了,数据就不可信。

