必须用after触发器,因before阶段事务未提交,回滚会导致审计日志与实际数据不一致;after触发器需按操作类型严格取值,禁赋值new/old,审计表字段须精简解耦,操作人和时间需可靠获取。

必须用 AFTER 触发器,否则审计日志和实际数据必然不一致——这是最核心的判断,没有例外。
为什么不能用 BEFORE 触发器做审计
审计要记录“已经发生的事”,不是“打算做的事”。BEFORE 阶段事务还没提交,一旦后续 SQL 报错或手动 ROLLBACK,你日志里写的变更根本没落地,但日志已写入,导致严重不一致。
-
BEFORE INSERT中OLD根本不存在,访问直接报Unknown column 'OLD.id' in 'field list' -
BEFORE DELETE中NEW为空,同理报错 -
BEFORE UPDATE虽然OLD和NEW都可用,但若触发器内部出错(比如字段长度超限),整个业务事务会跟着回滚——审计没记成,主流程反而挂了
AFTER INSERT/AFTER UPDATE/AFTER DELETE 各自怎么写才不出错
每个操作类型必须单独建触发器,且严格按上下文取值:只在可用时读,不在可用时绝不硬读。
-
AFTER INSERT:只用NEW.*,此时自增 ID 已确定,可安全写入日志表 -
AFTER UPDATE:必须加字段比对,例如IF OLD.status != NEW.status THEN ... END IF;,否则无变更也记日志,量爆炸 -
AFTER DELETE:只用OLD.*,这是唯一能拿到被删行完整数据的时机 - 所有
AFTER触发器中,禁止给NEW或OLD赋值(如SET NEW.name = 'x'),会报Can't update NEW row in after trigger
审计表结构设计最容易翻车的三个点
不是字段越多越好,而是越精简、越解耦越稳。
-
pk_value必须是VARCHAR(255),不能照搬原表主键类型——UUID、复合主键、JSON 主键都得转字符串存 -
old_data和new_data必须用JSON类型(MySQL 5.7+),别用TEXT拼接,否则JSON_OBJECT()构造失败,且易注入、难解析 - 绝对不要加外键、不要对
JSON字段建索引——JSON内容无法高效索引,加了反而拖慢写入;外键会导致删原表失败
触发器里获取操作人和时间的可靠写法
USER() 返回的是 user@host,生产环境经代理或连接池后 IP 不可靠;CURRENT_USER() 返回授权账号(如 app_user@'%'),丢失具体操作人。
- 首选:应用层执行业务 SQL 前先
SET @audit_user = 'alice',触发器中读@audit_user - 次选:用
SUBSTRING_INDEX(USER(), '@', 1)截用户名,但需 DBA 统一规范账号命名 - 时间统一用
NOW(3),别用CURRENT_TIMESTAMP——后者语义模糊,部分版本行为不一致,毫秒级误差会影响排查顺序
真正难的不是写触发器,而是让每个字段长度、每处 NULL 处理、每次 JSON 构造都扛住线上批量和边界数据——这些细节不出问题时完全隐形,一出就是日志写不进、主表锁死、复制中断。











