INSERT触发器仅可用NEW,DELETE触发器仅可用OLD,UPDATE触发器二者皆可;字段名须严格匹配表定义,NULL比较需用<=>或显式空值判断,OLD始终只读,NEW仅BEFORE中可写。

MySQL触发器里直接用 OLD 和 NEW 就能拿到新旧字段值,但必须匹配操作类型和触发时机,错一个就报错或读不到。
哪些触发器能访问 OLD 和 NEW
不是所有触发器都能同时用上这两个伪记录:
-
INSERT触发器:只有NEW可用,OLD一访问就报ERROR 1363 (HY000): There is no OLD row in on INSERT trigger -
DELETE触发器:只有OLD可用,NEW会触发NEW is not available in DELETE triggers -
UPDATE触发器:OLD和NEW都可用,且仅在此类触发器中二者共存
常见误操作是写了个 AFTER INSERT 触发器,却试图读 OLD.id —— 这不是逻辑问题,是语法禁区,MySQL 直接拒绝执行。
OLD.col 和 NEW.col 怎么写才不报错
字段名必须和表定义一字不差,包括大小写、下划线、空格和关键字:
- 原表字段是
`order status`(带空格),就得写OLD.`order status`;写成OLD.order_status或OLD.orderstatus都会报Unknown column 'OLD.xxx' in 'field list' - 字段名是
user_id,不能写成OLD.userId(驼峰)→ 返回NULL或报错 - 字段名是
order(MySQL 关键字),不加反引号会触发ERROR 1064 - 若 MySQL 配置了
lower_case_table_names=1,但建表时用了大写字段如Name,则OLD.Name可能静默失败,得用OLD.name(小写)
别指望 MySQL 帮你自动转换大小写,它只认建表时写的那个字符串。
怎么安全判断某个字段“真的被改了”
直接用 OLD.col != NEW.col 在遇到 NULL 时会失效,因为 NULL != NULL 返回 UNKNOWN,条件不成立。正确做法是显式处理空值:
- 推荐写法:
NOT (OLD.col NEW.col)(是 MySQL 的空安全等于) - 兼容老版本可选:
IFNULL(OLD.col, '') != IFNULL(NEW.col, ''),但注意类型隐式转换风险(比如数字字段转空字符串后比较) - 对 TEXT/BLOB 字段,某些 MySQL 5.7 版本在触发器中调用
CONCAT(OLD.content, ' updated')可能返回NULL——不是OLD没拿到,而是大字段上下文处理异常,建议改用CAST(OLD.content AS CHAR)显式转类型
别省这一步。很多审计日志漏记录,根源就是字段值为 NULL 时比对逻辑崩了。
BEFORE 和 AFTER 对 NEW 的修改权限差异
只有 BEFORE UPDATE 和 BEFORE INSERT 允许改 NEW.col,AFTER 阶段改了也白改,还会报错:
-
BEFORE UPDATE中写SET NEW.updated_at = NOW()→ 生效 -
AFTER UPDATE中写同一条语句 → 报ERROR 1362 (HY000): Updating of NEW row is not allowed in trigger -
OLD在任何阶段都不可写,尝试赋值一律报ERROR 1362
如果你发现 NEW 字段没更新成功,第一反应不该是查语法,而是用 SHOW CREATE TRIGGER trigger_name 确认它是不是定义在 AFTER 上。
真正容易被忽略的是:你看到日志里某字段值为空,第一反应常是“OLD 没拿到”,但大概率是字段定义为 TEXT 或 BLOB,且触发器里用了函数拼接导致返回 NULL —— 这不是触发器机制失效,而是 MySQL 对大字段在触发器上下文中的行为边界没摸清。


















