MySQL触发器中用OLD和NEW获取更新前后字段值:INSERT只能用NEW,DELETE只能用OLD,UPDATE可同时用二者;比较时需用<=>避免NULL问题,且BEFORE中NEW可修改、AFTER中只读。

触发器里怎么拿到更新前后的字段值
MySQL 触发器中,OLD 和 NEW 是只读的行级虚拟表,分别代表修改前和修改后的整行数据。它们不是变量,不能赋值,但可以像访问普通记录一样用点号取字段:OLD.id、NEW.name。
常见错误是试图在 INSERT 触发器里读 OLD(会报错 ERROR 1363 (HY000): There is no OLD row in on INSERT trigger),或在 DELETE 里读 NEW(同理报错)。只有 UPDATE 触发器才能同时访问两者。
-
BEFORE INSERT:只能用NEW,可修改其字段值(比如生成默认时间、校验并抛错) -
BEFORE UPDATE:OLD不可改,NEW可改(常用于规范化输入、拦截非法变更) -
AFTER DELETE:只能用OLD(比如记录删除日志、归档旧数据)
UPDATE 触发器中判断字段是否真被修改了
直接比较 OLD.col = NEW.col 在遇到 NULL 时会失效(NULL = NULL 返回 NULL,不是 TRUE)。必须用 IS NULL 显式判断,或用 安全等于操作符。
例如想只在 status 字段实际变化时才更新关联表:
CREATE TRIGGER update_order_log AFTER UPDATE ON orders
FOR EACH ROW
BEGIN
IF OLD.status <=> NEW.status THEN
INSERT INTO order_logs(order_id, old_status, new_status)
VALUES (NEW.id, OLD.status, NEW.status);
END IF;
END;- 用
<=>比=更安全,它把NULL当作普通值处理 - 避免写
IF OLD.status != NEW.status—— 任一为NULL就跳过逻辑 - 如果字段类型是 JSON 或 TEXT,某些 MySQL 版本下
<=>可能不支持,此时得拆成(OLD.col IS NULL) != (NEW.col IS NULL) OR OLD.col != NEW.col
触发器里调用函数或查询其他表要注意什么
MySQL 触发器中禁止对**同一张表**做 DML(INSERT/UPDATE/DELETE),否则会报错 ERROR 1442 (HY000): Can't update table 'xxx' in stored function/trigger because it is already used by statement which invoked this stored function/trigger。
但可以查(SELECT),也可以对其他表执行任意操作。不过要注意性能和事务一致性:
- 不要在触发器里做耗时查询(比如 JOIN 多张大表),它会拖慢主语句执行,且无法异步
- 如果触发器里
INSERT到日志表失败(比如磁盘满、权限不足),整个原事务会回滚 —— 这是设计使然,不是 bug - 想解耦可考虑用
INSERT ... SELECT写入中间缓存表,再由外部定时任务消费,而不是在触发器里直连远程服务或发 HTTP 请求
为什么 AFTER 触发器里改不了 NEW 字段
NEW 只在 BEFORE 触发器中可写;到了 AFTER 阶段,语句已执行完毕,NEW 仅作只读快照使用。尝试赋值会报错 ERROR 1362 (HY000): Updating of NEW row is not allowed in after trigger。
这意味着:想“修正”用户提交的数据,必须用 BEFORE UPDATE;想基于最终结果做后续动作(比如通知、统计),必须用 AFTER。
- 误在
AFTER里写SET NEW.amount = NEW.amount * 1.1→ 直接报错,不会静默忽略 - 跨引擎表(比如 MyISAM + InnoDB)混合操作时,
AFTER触发器可能因引擎不支持事务而行为异常,优先统一用 InnoDB - 触发器嵌套深度默认为 0(即不允许递归触发),若需开启,要设
max_sp_recursion_depth > 0,但极易引发死循环,慎用
最常被忽略的是:触发器不继承外层事务的隔离级别,也不受 autocommit 设置影响 —— 它总在当前语句的事务上下文中运行,哪怕你手动关了自动提交,它依然跟着走。










