在MySQL触发器中,OLD.col和NEW.col分别用于访问UPDATE/DELETE和INSERT/UPDATE前后的字段值,需注意触发器类型限制、NULL安全比较(用)及不可赋值等规则。

触发器里怎么拿到 OLD 和 NEW 的字段值
MySQL 触发器本身不提供自动比对字段差异的函数,但你可以直接用 OLD.字段名 和 NEW.字段名 访问更新前后的值。关键前提是:必须在 BEFORE UPDATE 或 AFTER UPDATE 触发器中使用,且仅对被修改的列有效(比如某列没出现在 UPDATE 语句的 SET 子句里,NEW.字段名 仍等于原值,不是 NULL)。
常见错误是误以为 OLD 在 INSERT 触发器中可用(实际会报错 ERROR 1363: There is no OLD row in on INSERT statement),或在 BEFORE DELETE 中误读 NEW(它不存在)。
-
OLD.col在UPDATE和DELETE触发器中可用,代表变更前的值 -
NEW.col在INSERT和UPDATE触发器中可用,代表变更后的值 - 若字段允许 NULL,需用
IS NULL判断,不能用= NULL
判断某个字段是否真的被修改了
直接比较 OLD.col != NEW.col 不够安全:当字段类型为 VARCHAR 且含尾部空格时,MySQL 默认按 PAD SPACE 规则比较,'a ' 和 'a' 会被判为相等;NULL 值之间比较结果为 UNKNOWN,导致条件不成立。
稳妥写法是显式处理 NULL 和类型敏感比较:
IF (OLD.name IS NULL AND NEW.name IS NOT NULL) OR (OLD.name IS NOT NULL AND NEW.name IS NULL) OR (OLD.name != NEW.name AND NOT (OLD.name <=> NEW.name)) THEN -- 字段 name 确实变了 END IF;
更简洁的方式是用 MySQL 的 NULL 安全等于操作符 :
-
OLD.col NEW.col返回 0 表示不等(即有变化),1 表示相等(含两者都为 NULL) - 所以判断变化的条件可简化为
NOT (OLD.col NEW.col) - 注意:
在所有 MySQL 版本中都支持,但某些旧客户端可能解析异常,建议在触发器内使用
把差异字段和值记录到日志表里
如果目标是审计,通常需要把哪些字段变了、从什么值变成什么值存进日志表。这时不能硬编码字段名,得靠触发器逻辑逐个判断——MySQL 触发器不支持动态列名,所以必须手动列出要监控的字段。
例如,监控 users 表的 email 和 status 变化:
IF NOT (OLD.email <=> NEW.email) THEN INSERT INTO user_audit (user_id, field, old_value, new_value, updated_at) VALUES (OLD.id, 'email', OLD.email, NEW.email, NOW()); END IF; IF NOT (OLD.status <=> NEW.status) THEN INSERT INTO user_audit (user_id, field, old_value, new_value, updated_at) VALUES (OLD.id, 'status', OLD.status, NEW.status, NOW()); END IF;
容易踩的坑:
- 日志表字段类型要兼容源字段(如
old_value建议用TEXT,避免截断长文本) - 触发器里执行
INSERT属于隐式事务操作,若日志表写入失败(如磁盘满、权限不足),整个UPDATE会回滚 - 不要在触发器里调用存储函数做复杂逻辑,容易拖慢主 DML 性能
为什么不能用 JSON_OBJECT 自动构造差异对象
有人想用 JSON_OBJECT('email', IF(NOT (OLD.email NEW.email), JSON_OBJECT('from', OLD.email, 'to', NEW.email), NULL)) 一类方式“自动”生成差异结构,但 MySQL 触发器中无法将动态生成的 JSON 转成字段列表插入,也没办法遍历表结构元数据(INFORMATION_SCHEMA.COLUMNS 在触发器中不可查)。
换言之:没有元数据反射能力,就做不到通用差异捕获。如果真需要通用方案,得在应用层做 diff,或用 BINLOG 解析(如 Maxwell、Debezium),而不是依赖触发器。
最常被忽略的一点:触发器里的 OLD/NEW 是只读的,你不能给它们赋值,也不能用变量间接修改行数据——所有变更必须通过后续 UPDATE 语句显式完成,而那又会再次触发同类型触发器,极易造成无限递归。


















