INSERT触发器只能用NEW,DELETE触发器只能用OLD,UPDATE触发器两者皆可用;字段名须严格匹配表定义(含大小写、下划线、空格),否则静默返回NULL或报错;NEW仅在BEFORE中可修改,AFTER中赋值无效或报错;比较OLD与NEW时需用空值安全操作符<=>或手动判空,避免NULL导致逻辑失效。

INSERT/UPDATE/DELETE 触发器里哪些能用 NEW,哪些能用 OLD
硬性规则,错一个就报错:INSERT 触发器只能用 NEW,DELETE 触发器只能用 OLD,UPDATE 触发器两者都可用。不是“建议”,是 MySQL 强制限制。
常见错误现象:
-
INSERT触发器里写OLD.id→ 报错ERROR 1363: There is no OLD row in on INSERT trigger -
DELETE触发器里写NEW.name→ 报错NEW is not available in DELETE triggers -
UPDATE触发器里误把OLD.status写成OLD.Status(大小写不匹配)→ 静默返回NULL,日志里字段为空却查不到原因
字段名必须严格匹配表定义(包括大小写、下划线、空格),不确定就先执行 DESCRIBE your_table 确认真实拼写。
BEFORE 和 AFTER 阶段对 NEW 赋值是否生效
NEW 字段只在 BEFORE 触发器中可修改,AFTER 中赋值完全无效,还可能触发报错。
典型错误场景:
- 在
AFTER INSERT里写SET NEW.created_at = NOW()→ 不报错但无效果,字段仍是默认值或 NULL - 在
AFTER UPDATE里写SET NEW.updated_at = NOW()→ 报错Can't update table 'xxx' in stored function/trigger(因为数据已落盘,且不能改自身) - 正确做法:用
BEFORE INSERT填充默认时间,用BEFORE UPDATE更新时间戳或计算衍生字段(如NEW.amount = (SELECT price FROM goods WHERE id = NEW.goods_id) * NEW.qty)
判断当前触发器类型?执行 SHOW CREATE TRIGGER trigger_name,看开头是 BEFORE 还是 AFTER。
比较 OLD 和 NEW 字段时 NULL 导致逻辑失效
OLD.status != NEW.status 在任一端为 NULL 时,整个表达式恒为 NULL(即 false),条件永远不成立——这是最常被忽略的坑。
安全写法只有两种:
- 用空值安全等于操作符:
NOT (OLD.status NEW.status) - 手动拆解判断:
(OLD.status IS NULL) != (NEW.status IS NULL) OR OLD.status != NEW.status
注意: 在 JSON 或某些大文本字段(如 TEXT)上部分 MySQL 版本可能不支持,此时必须用第二种写法。
字段含空格、关键字或大小写敏感时怎么引用
MySQL 不校验字段是否存在,直到触发时才出问题。一旦字段名带空格、关键字(如 order、status)或建表时用了双引号定义(如 "Name"),就必须加反引号包裹,否则直接报语法错误。
错误示例:
-
OLD.order→ 报错ERROR 1064(order是保留字) -
OLD.user name→ 报错(含空格未转义) - 建表时写了
"CreatedAt",触发器里却写OLD.CreatedAt→ 返回NULL(大小写+双引号需完全一致)
正确写法统一为:OLD.`order`、OLD.`user name`、OLD.`CreatedAt`。
真正容易被忽略的是:日志里某字段显示为空,第一反应常是“OLD 没拿到”,但大概率是字段类型为 TEXT 或 BLOB,加上 CONCAT 等函数调用导致上下文异常,并非 OLD/NEW 机制本身失效。


















