mysql触发器中,before update用old/new取变更前后值,after insert/delete仅用new/old;审计表需独立主键、枚举action、同步now()、字段长度一致、应用传user_id/ip;审计日志一律用after触发器避免不一致。

触发器里怎么拿到变更前后的值
MySQL 的 BEFORE UPDATE 和 AFTER INSERT 触发器中,用 OLD.字段名 拿变更前值,NEW.字段名 拿变更后值。注意 INSERT 没有 OLD,DELETE 没有 NEW,强行引用会报错 Unknown column 'OLD.id' in 'field list'。
常见写法误区是统一写 OLD.* 或 NEW.* —— 触发器不支持这种通配,必须显式列出字段。
-
INSERT场景只用NEW.id,NEW.name等 -
UPDATE场景可对比:IF OLD.status != NEW.status THEN ... -
DELETE场景只用OLD.id,OLD.created_at等
审计表结构设计要注意什么
审计表不能简单照搬原表结构。主键要独立(比如加自增 id),原表主键作为普通字段存为 record_id;操作类型用枚举字段 action ENUM('INSERT','UPDATE','DELETE'),别用字符串避免索引失效;时间必须用 NOW() 而非 SYSDATE(),后者在触发器中可能因事务延迟产生歧义。
容易被忽略的是字段长度:如果原表 content TEXT,审计表对应字段也得是 TEXT,否则截断。同理,JSON 字段要保留完整精度,别转成 VARCHAR(255)。
- 加
user_id字段记录操作人(需应用层传入,触发器本身拿不到 session 用户) - 加
ip_address字段(同样需应用层提供,MySQL 触发器无法获取客户端 IP) - 避免在审计表上建过多索引,写入放大严重;至少保证
(record_id, action, created_at)有联合索引
INSERT/UPDATE/DELETE 各自该用什么触发器时机
原则是:想记录“最终结果”用 AFTER,想做校验或拦截用 BEFORE。审计日志属于事后记录,一律用 AFTER 触发器 —— 否则在 BEFORE UPDATE 中插入审计日志,若后续 SQL 报错回滚,审计记录却已提交,造成日志与数据不一致。
-
AFTER INSERT:直接记NEW.*,无需判断 -
AFTER UPDATE:建议逐字段比对(如IF OLD.email != NEW.email),避免无意义的全量更新日志 -
AFTER DELETE:必须用OLD.*,且注意外键约束下可能已被级联删掉,但触发器仍能读到OLD值
一个表最多定义各一个 AFTER INSERT、AFTER UPDATE、AFTER DELETE 触发器,重复定义会报错 Can't create more than one trigger with the same action time and event for one table。
性能和维护上的现实约束
触发器是同步执行的,每条 DML 都会额外多一次 INSERT 到审计表,QPS 高时容易成为瓶颈。线上表超过 10 万行、TPS > 200 时,就得考虑异步方案(比如 Binlog 解析 + Kafka + Flink 写审计库)。
另一个硬伤是调试困难:触发器报错默认不透出给应用,只记在 MySQL 错误日志里,且堆栈不包含触发器名。上线前务必用 SELECT 模拟逻辑测试,例如:
INSERT INTO user (name, email) VALUES ('test', 't@x.com');
然后立刻查审计表验证字段是否齐全、时间是否准确、action 是否为 'INSERT'。
最常被绕过的点:ALTER 原表结构后,触发器不会自动适配新字段,审计日志会缺失新增列的变更记录,必须手动补上触发器逻辑并重新创建。










