MySQL触发器中需用空安全比较OLD.col <=> NEW.col替代!=,因NULL参与比较结果为UNKNOWN;须显式比对值变化,禁用不存在的UPDATE()函数,且结构变更后必须手动更新触发器逻辑。

MySQL触发器里用 OLD.col_name != NEW.col_name 会漏掉 NULL 变更
直接写 OLD.email != NEW.email 在字段从 NULL 改成 'a@b.com' 或反过来时,结果是 UNKNOWN,不是 TRUE 或 FALSE,导致 IF 判断直接跳过——日志没记,变更就丢了。
必须改用空安全等于操作符:OLD.email NEW.email。它在两边都为 NULL 时返回 1(相等),一 NULL 一非 NULL 时返回 0(不等),能真正反映值是否变化。
- 别写
OLD.col IS NULL AND NEW.col IS NOT NULL这类组合条件,易漏、难读、难扩展 - 字符串大小写敏感比对要加
BINARY:例如BINARY OLD.name BINARY NEW.name - 大文本字段(如
TEXT)慎用全值比对,可考虑哈希后比较:MD5(OLD.content) != MD5(NEW.content),但注意哈希碰撞极小概率存在
别信 UPDATE('col_name'),MySQL 根本不支持这个函数
SQL Server 有 UPDATE() 函数,PostgreSQL 有 pg_trigger_depth(),但 MySQL 没有对应语法。你在 MySQL 里写 IF UPDATE('email'),会报错 Unknown function 'UPDATE'。
UPDATE() 是 SQL Server 特有的,只表示该字段出现在 SET 子句中,哪怕设成和原来一样的值也算“被更新”——它不能代替值比对。
- MySQL 唯一可靠方式就是显式比对:
OLD.email NEW.email - 想监控多个列,用布尔表达式合并:
(OLD.status NEW.status) OR (OLD.amount NEW.amount) - 不要嵌套
IF:避免IF OLD.a != NEW.a THEN IF OLD.b != NEW.b THEN ...,逻辑断裂且无法捕获单列变更
触发器里记录变更前先确认是否真变了,否则日志爆炸
不加判断直接往日志表插数据,哪怕 UPDATE users SET name='x' WHERE id=1,也会把所有列(包括没动的 created_at、updated_at)全记一遍,日志表几天就撑爆。
必须用 IF 包裹每列的判断逻辑,且只对业务关键字段审计——比如只盯 status、email、balance,别管 updated_at 自增字段。
- 表结构变更(如加了
phone列)后,触发器不会自动适配,必须人工补上OLD.phone NEW.phone判断 - 避免在触发器里调用函数或查表:
SELECT COUNT(*) FROM log WHERE user_id = NEW.id这种语句会让批量更新性能断崖下跌 - 操作人信息不能依赖
USER()(返回连接账户),应用层得提前设会话变量:SET @current_user = 'admin-api',再在触发器里用COALESCE(@current_user, USER())
用 BEFORE UPDATE 而不是 AFTER UPDATE 记录变更
只有 BEFORE UPDATE 触发器里 OLD 和 NEW 都可用,且你能在主语句执行前插入日志或修改 NEW 值(比如自动更新 updated_at)。
AFTER UPDATE 虽然也能访问 OLD/NEW,但主 DML 已提交,若日志插入失败(比如磁盘满、权限不足),会导致事务不一致——主表改了,日志却没落库。
-
BEFORE UPDATE中可做轻量拦截:比如IF OLD.status = 'closed' AND NEW.status != 'closed' THEN SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'Cannot reopen closed record'; - 日志表本身建议用
ENGINE=InnoDB并加索引:INDEX (table_name, column_name, modified_at),避免查询变慢 - 高并发场景下,触发器内避免
INSERT ... SELECT或子查询,它们容易引发锁等待甚至死锁
IF 判断,都是硬编码的契约——结构变,代码就得同步改,否则审计就失效。


















