MySQL触发器中UPDATE()函数仅检查字段是否出现在SET子句中,不判断值是否真实变化;应使用OLD.col <=> NEW.col比较,并用NOT (OLD.col <=> NEW.col)检测变更。

MySQL触发器里不能用 UPDATE() 判断“值是否真变了”
UPDATE() 函数只检查 SQL 语句的 SET 子句里有没有出现该字段名,不管新旧值是否相同。比如执行 UPDATE users SET email = OLD.email,UPDATE('email') 仍返回 TRUE,但实际值根本没变。
更关键的是:UPDATE() 只在 BEFORE UPDATE 或 AFTER UPDATE 触发器中可用;放在 INSERT 或 DELETE 触发器里会直接报错 FUNCTION xxx.UPDATE does not exist。
所以别依赖它做业务逻辑判断——它不是为“检测变更”设计的,只是个语法存在性检查。
真正判断字段值是否变化,必须显式比对 OLD.col 和 NEW.col
MySQL 没有 IS DISTINCT FROM(那是 PostgreSQL 的),但提供了安全等于运算符 ,能正确处理 NULL:
-
OLD.email NEW.email返回1表示相等(含NULL NULL) -
NOT (OLD.email NEW.email)才表示“值确实变了” - 别用
!=或:一旦任一端为NULL,整个表达式结果是UNKNOWN,IF就不会进入分支 - 字符串字段注意尾部空格和 collation;必要时加
TRIM()或用BINARY OLD.name != BINARY NEW.name
多个字段要同时监控,用布尔表达式拼接,别嵌套 IF
比如要记录“只要 status 或 updated_at 中任意一个变了就写日志”,写成:
IF NOT (OLD.status <=> NEW.status) OR NOT (OLD.updated_at <=> NEW.updated_at) THEN INSERT INTO audit_log (...) VALUES (...); END IF;
而不是:
IF NOT (OLD.status <=> NEW.status) THEN ... ELSEIF NOT (OLD.updated_at <=> NEW.updated_at) THEN ... END IF;
后者漏掉两者都变的情况,且难以扩展。后续加字段只需在 OR 后追加一项,结构清晰、维护成本低。
别在触发器里查表、调函数或写复杂逻辑
每次更新一行就触发一次,以下操作会明显拖慢主 DML:
- 在触发器里执行
SELECT ... FROM logs WHERE user_id = NEW.user_id—— 100 行更新就查 100 次 - 调用
UUID()、SLEEP()或自定义函数(尤其含 I/O 或网络请求) - 跨表
UPDATE却不检查影响行数:ROW_COUNT() = 0说明没匹配到目标记录,逻辑可能静默失效
真正需要审计或联动的重逻辑,应该挪到应用层或通过异步任务消费 binlog 实现。触发器只做轻量判断和字段赋值,比如 NEW.updated_at := NOW() 或 NEW.version := OLD.version + 1。
最常被忽略的一点:触发器不会在“无实际变更”的 UPDATE 上运行。例如 UPDATE t SET name = name WHERE id = 1,MySQL 8.0+ 默认跳过整条语句(连触发器都不触发),且不报错。如果业务依赖触发器兜底,得确保应用层发出的 SQL 真正带有效变更,或改用 BEFORE UPDATE 中主动赋值来规避。


















