MySQL触发器中NEW和OLD是只读伪记录,非变量;INSERT仅用NEW(BEFORE可修改)、DELETE仅用OLD、UPDATE两者皆可;字段名须严格匹配表结构,否则静默返回NULL。

MySQL触发器里不能把 NEW 和 OLD 当成变量声明或赋值,它们是只读的伪记录(transition variables),字段访问必须严格匹配表结构定义,否则静默返回 NULL —— 这是最容易踩坑的地方。
INSERT 触发器只能用 NEW,且仅 BEFORE 阶段可修改
INSERT 操作没有“旧数据”,所以 OLD 在 INSERT 触发器中完全不可用,哪怕只是读取也会报错:ERROR 1363 (HY000): There is no OLD row in on INSERT statement。
-
BEFORE INSERT中可以写SET NEW.created_at = NOW()或SET NEW.uuid = UUID(),用于填充默认值或清洗数据 -
AFTER INSERT中NEW已落库,只读;此时再对NEW.id赋值(比如SET NEW.id = LAST_INSERT_ID())完全无效 - 如果目标字段有默认值(如
created_at DATETIME DEFAULT CURRENT_TIMESTAMP),在BEFORE中显式设值可能冲突,建议先检查字段定义
UPDATE 触发器中 NEW 和 OLD 必须同时存在,但用途不同
UPDATE 是唯一能同时访问新旧值的场景,但要注意:字段是否真被修改,不取决于 SQL 语句写了什么,而取决于实际值是否变化 —— 即使写 UPDATE t SET name = name,触发器仍会执行,OLD.name == NEW.name 为 true。
- 判断字段是否“真正变更”必须处理 NULL:
NOT (OLD.status NEW.status)(推荐)或(OLD.status IS NULL) != (NEW.status IS NULL) OR OLD.status != NEW.status -
BEFORE UPDATE可修改NEW.column(比如自动更新updated_at),但改主键/唯一键字段可能引发Duplicate entry错误,需提前校验 -
AFTER UPDATE中NEW不可写,但可用于日志插入:INSERT INTO log_table SET user_id = NEW.id, old_status = OLD.status, new_status = NEW.status
DELETE 触发器只能用 OLD,且字段名大小写和反引号必须精确匹配
NEW 在 DELETE 触发器中非法,引用即报错:ERROR 1363 (HY000): There is no NEW row in on DELETE statement。而 OLD 是唯一能拿到被删行数据的方式,但它不是“已删除的数据副本”,而是操作前那一行的快照。
- 字段名必须和
DESCRIBE table_name输出完全一致,比如建表时是user_name,就不能写成OLD.username或OLD.USER_NAME,否则静默返回NULL - 含空格或横线的列名必须用反引号:
OLD.`order-id、OLD.`full name - 对
TEXT/BLOB类型字段,某些 MySQL 版本(如 5.7)在触发器中直接拼接可能返回NULL,建议包一层:IFNULL(OLD.content, '') - 跨库写日志时,不能写
INSERT INTO other_db.log SELECT * FROM OLD(语法错误),正确写法是:INSERT INTO other_db.log (id, name) VALUES (OLD.id, OLD.name)
触发器中引用 NEW/OLD 的常见错误和权限陷阱
所有触发器都运行在原 SQL 的同一事务中,一旦触发器内语句失败(如日志表字段超长、磁盘满、权限不足),整个原始操作(INSERT/UPDATE/DELETE)都会回滚 —— 这不是 bug,是 ACID 的强制保证。
-
SHOW CREATE TRIGGER trigger_name是排查时机问题的第一步,确认开头是BEFORE还是AFTER;写成AFTER却想改NEW,永远不生效 -
FOR EACH ROW是强制要求,漏写或误写成FOR EACH STATEMENT会导致创建失败,报错类似ERROR 1363 - 跨库操作需要触发器定义用户对目标库表有对应权限,且必须用完整限定名(如
other_db.log_table),不能带OLD前缀(db1.OLD.col非法) - 避免在触发器里对
OLD字段做子查询或函数计算(如CONCAT(OLD.text, ' deleted')),性能差,还可能因隐式转换失败
最常被忽略的是字段名大小写和 NULL 安全比较 —— 它们不会报错,只会让逻辑静默失效,查起来非常费时间。


















