必须选after,因为审计需确保操作已生效且捕获最终结果;before下new值可能被修改、old在insert时不存在,且事务未提交易留无效日志。

触发器该用 AFTER 还是 BEFORE?为什么必须选 AFTER
记录审计日志的核心诉求是:确保被审计的操作已真实生效,且能捕获最终变更结果。若用 BEFORE 触发器,NEW 值可能被后续语句修改(比如触发器里又改了字段),而 OLD 在 INSERT 时根本不存在;更关键的是,事务尚未提交,日志写入后若事务回滚,就会留下无效记录。
所以对审计场景,AFTER INSERT、AFTER UPDATE、AFTER DELETE 是唯一合理选择——此时数据已落盘(或至少在事务内可见),OLD 和 NEW 的值稳定可读。
-
AFTER INSERT:只读NEW.*,OLD不可用 -
AFTER UPDATE:OLD.<em></em>是更新前值,NEW.是更新后值 -
AFTER DELETE:只读OLD.*,NEW不可用
审计表结构怎么设计才不拖慢主表
审计表不是越全越好。字段冗余、索引滥用、无条件写入都会让触发器成为性能瓶颈。实际部署中,常见错误是直接把主表所有字段原样复制进审计表,甚至加全文索引。
建议按需裁剪:
- 必填字段:
id(自增)、table_name(varchar(64))、operation(enum('INSERT','UPDATE','DELETE'))、row_id(记录被操作行的主键值,bigint 或 varchar,取决于主键类型)、old_values和new_values(json 类型,存差异字段,不是全量) - 时间字段用
NOW(3)而非CURRENT_TIMESTAMP,避免时区干扰 - 禁止在审计表上建除主键外的二级索引——除非你明确要查某张表某段时间的全部变更,否则索引维护开销远超查询收益
- 表引擎用
InnoDB,但关闭autocommit时注意:触发器内写审计表会绑定到主事务,回滚则日志也消失(这是正确行为)
触发器体里怎么安全拼接 JSON 并避开 NULL 陷阱
MySQL 5.7+ 支持 JSON_OBJECT() 和 JSON_CONTAINS(),但直接用 JSON_OBJECT('col', NEW.col) 会在 col 为 NULL 时导致整个 JSON 构造失败(报错 Invalid JSON text)。
正确做法是显式过滤 NULL,并只记录实际变化的字段:
-
UPDATE场景下,用IF(OLD.col != NEW.col, JSON_OBJECT('col', NEW.col), NULL)判断变更 - 再用
JSON_MERGE_PATCH()合并多个字段的 JSON 片段(比CONCAT拼字符串安全得多) - 最终插入审计表时,用
COALESCE(..., '{}')确保字段不为 NULL
示例片段(简化版):
SET @old_json = COALESCE(
JSON_OBJECT('name', OLD.name, 'status', OLD.status),
'{}'
);
SET @new_json = COALESCE(
JSON_OBJECT('name', NEW.name, 'status', NEW.status),
'{}'
);
INSERT INTO audit_log (table_name, operation, row_id, old_values, new_values)
VALUES ('user', 'UPDATE', NEW.id, @old_json, @new_json);
注意:JSON_OBJECT() 对 datetime、blob 类型字段会报错,需先转成字符串:DATE_FORMAT(NEW.updated_at, '%Y-%m-%d %H:%i:%s')。
触发器无法记录执行用户和客户端 IP 怎么办
USER() 和 CURRENT_USER() 返回的是连接用户(如 'app@10.0.1.%'),不是应用层传来的操作人 ID;@@hostname 也只返回 MySQL 服务器主机名。
真实审计需要关联业务用户,有两条路:
- 应用层在 SQL 中显式传参:比如在
UPDATE user SET name=? WHERE id=?后追加/<em> user_id=123 </em>/注释,再配合 MySQL 8.0+ 的performance_schema.events_statements_history表反查(复杂且依赖权限) - 更可靠的做法:在应用写主表前,先写一条带业务用户信息的临时记录到中间表,触发器通过
JOIN或子查询关联获取——但这要求事务包含两步,且触发器不能跨表事务控制,所以实际生产中几乎都放弃在触发器里解决,改为应用层统一打日志 + 定期同步到审计库
真正容易被忽略的是:触发器本身没有事务隔离级别控制能力,如果主表更新涉及大字段(TEXT、BLOB),触发器内读取 OLD/NEW 可能引发隐式锁等待,导致并发下降。这时候,宁可接受少量日志延迟,也要考虑用消息队列异步落库,而不是硬扛在触发器里。











