MySQL触发器通过OLD和NEW获取修改前后数据:INSERT仅用NEW,DELETE仅用OLD,UPDATE两者皆可用;审计表应包含table_name、row_id等字段并建联合索引,变更字段需用NULL安全比较,避免性能陷阱。

触发器里怎么拿到修改前后的数据
MySQL 触发器通过 OLD 和 NEW 两个关键词访问行级变更数据,但必须严格匹配触发时机:BEFORE UPDATE 和 AFTER UPDATE 都能用它们,而 BEFORE INSERT 只能读 NEW(还没旧值),BEFORE DELETE 只能读 OLD(新值不存在)。
常见错误是写 AFTER DELETE 触发器还试图引用 NEW.id,结果报错 Unknown column 'NEW.id' in 'field list'。
-
INSERT:只有NEW可用,代表即将插入的行 -
UPDATE:OLD是原值,NEW是更新后值,字段名需显式写出,不能用* -
DELETE:只有OLD可用,代表被删的那行
审计日志表结构怎么设计才实用
审计表不是越全越好,关键是字段可索引、易查询、不拖慢主业务。别直接把整行 JSON 存进一个 TEXT 字段——查某次谁改了 price 就得全表扫描。
推荐结构包含:table_name、row_id(对应主表主键值)、action('INSERT'/'UPDATE'/'DELETE')、changed_fields(JSON 或逗号分隔字符串)、old_values 和 new_values(JSON 格式,只存实际变化的字段)、user_host(用 USER() 获取)、created_at(用 NOW())。
注意:row_id 类型必须和原表主键一致(比如原表是 BIGINT UNSIGNED,这里就不能用 INT),否则关联查询会隐式转换,索引失效。
UPDATE 触发器里如何只记录真正变化的字段
不能无脑把所有 NEW.col = OLD.col 都写进日志,否则日志膨胀快、查询噪音大。得逐字段判断是否真的变了,尤其要注意 NULL 比较:直接写 OLD.col != NEW.col 在任一为 NULL 时结果是 UNKNOWN,导致条件不成立。
正确写法用 IS NULL 显式判断:
IF NOT (OLD.name <=> NEW.name) THEN
SET @changed = CONCAT(@changed, '"name":', JSON_OBJECT('old', OLD.name, 'new', NEW.name), ',');
END IF;
-
是 MySQL 安全等于操作符,能正确处理NULL对比 - 避免在触发器里拼接超长字符串,建议用
JSON_OBJECT+JSON_MERGE_PATCH动态构建变更对象 - 如果主表字段多,优先监控关键业务字段(如
status、amount、owner_id),非核心字段可忽略
触发器性能和权限容易被忽略的点
触发器是同步执行的,INSERT 语句卡住,大概率是审计触发器里做了耗时操作,比如写磁盘文件、调外部 HTTP、或审计表没加索引导致 INSERT 慢。
另一个坑是权限:触发器以定义者(DEFINER)权限运行,不是调用者权限。如果用普通账号创建触发器但 DEFINER 是 root@localhost,上线后换账号部署可能报 Access denied for trigger execution。
- 审计表务必在
table_name、row_id、created_at上建联合索引,例如INDEX idx_audit_lookup (table_name, row_id) - 不要在触发器里做事务控制(
START TRANSACTION等),MySQL 不允许 - 测试时用
SHOW TRIGGERS LIKE 'orders'确认触发器状态,禁用用DROP TRIGGER,别用注释“临时关掉”
最麻烦的是跨库场景:OLD 和 NEW 只能引用当前表字段,不能直接写 other_db.audit_log 表名——得在触发器 SQL 里显式指定库名,否则默认写入当前数据库。


















