mysql触发器中判断字段是否变化需手动比较old和new值,正确使用安全等于、二进制转换、trim及is distinct from,并避免在insert/delete中误用update()函数。

直接比对 OLD 和 NEW 的字段值
MySQL 触发器里没有“字段值是否变化”的内置函数,必须手动比较 OLD.col 和 NEW.col。很多人误用 UPDATE('col'),但它只检测 SQL 语句里有没有写这个字段,哪怕 SET price = OLD.price 也返回 TRUE,完全不能反映真实变更。
正确做法是显式判断:
- 用
(安全等于)代替!=,避免 NULL 比较失效:例如IF NOT (OLD.email NEW.email)能正确捕获NULL → 'a@b.com'或'a@b.com' → NULL - 字符串字段要考虑 collation:默认排序规则下
'ABC' = 'abc'可能为TRUE,需强制二进制比较:CAST(NEW.name AS BINARY) != CAST(OLD.name AS BINARY) - 尾部空格问题:
'name '和'name'普通比较会相等,业务要求严格时加TRIM(),但注意TRIM(NULL)返回NULL,得先判空
别在 INSERT/DELETE 触发器里调用 UPDATE()
UPDATE() 函数只在 BEFORE UPDATE 或 AFTER UPDATE 触发器中合法。在 INSERT 触发器里写 IF UPDATE('id') 看似语法不报错,执行时直接抛出 FUNCTION xxx.UPDATE does not exist 错误。
如果想统一处理“某字段有变动”逻辑:
-
INSERT场景默认视为“首次赋值”,即字段从无到有,可直接当作变更处理 -
UPDATE场景才用UPDATE('col') AND (OLD.col IS DISTINCT FROM NEW.col)(MySQL 8.0.17+ 支持IS DISTINCT FROM,否则仍用) - 不要试图用同一个触发器覆盖 INSERT/UPDATE/DELETE —— 语义不同,硬凑只会增加歧义和维护成本
多个字段组合判断时避免嵌套 IF
监控 status 和 amount 两个字段是否任一发生实际变化,常见错误是写两层 IF 嵌套,既难读又漏场景(比如两者都变时只触发一次逻辑)。
推荐扁平化布尔表达式:
- 任一字段变:
IF (OLD.status NEW.status) = FALSE OR (OLD.amount NEW.amount) = FALSE THEN - 两个字段都变:
IF (OLD.status NEW.status) = FALSE AND (OLD.amount NEW.amount) = FALSE THEN - 后续要加新字段(如
updated_by),只需在条件末尾追加OR (OLD.updated_by NEW.updated_by) = FALSE,结构稳定
别写 IF ... THEN ... ELSEIF ... THEN,它本质是互斥分支,无法覆盖“多字段同时变”的审计需求。
审计日志字段级记录的实操陷阱
真正难的是把“哪个字段变了、从什么变成什么”可靠落库。常见翻车点:
-
old_value/new_value字段类型太小:比如用VARCHAR(255)存 JSON 字段变更,必然截断 —— 必须用TEXT或MEDIUMTEXT - 没建复合索引:
SELECT * FROM change_log WHERE table_name = 'users' AND row_id = 123在百万级日志表上可能秒变慢查询 —— 至少建(table_name, row_id, updated_at)索引 - 把所有变更拼成一条字符串日志(如
"status: pending→done, amount: 100→200"):查“所有 status 从 done 改为 cancelled 的记录”就得全表扫描 + 正则,没法走索引 - 在触发器里调
USER()或@@session.program_name获取操作人 —— 这些值常为空或不可靠,真实操作人信息必须由应用层通过SET @current_user = 'xxx'显式传入
最易被忽略的一点:触发器里任何 SELECT、子查询、远程调用,都会让主事务变慢甚至失败。审计逻辑务必轻量,只做字段比对和 INSERT 日志,别查表、别调函数、别连外部服务。











