必须用after触发器,因审计需记录真实生效的变更:insert时old不存在,before中new可能被覆盖且事务未提交易留脏数据;after下new和old值稳定可读,确保日志准确可靠。

能,但必须手动写逻辑、只用 AFTER 触发器、避开 OLD/NEW 误读和权限陷阱——否则日志要么漏记,要么拖垮主业务。
为什么必须用 AFTER INSERT/UPDATE/DELETE?
审计要记录“真实发生的变更”,不是“准备怎么改”。BEFORE 触发器里:OLD 在 INSERT 时根本不存在;NEW 值可能被后续语句覆盖;事务还没提交,万一回滚,日志就成脏数据。而 AFTER 下:INSERT 稳定可读 NEW.*,UPDATE 同时有 OLD.* 和 NEW.*,DELETE 可靠拿到 OLD.*。
-
AFTER INSERT:直接取NEW.id、NEW.email等字段,不用比对 -
AFTER UPDATE:必须显式判断OLD.col != NEW.col,否则无变更也写日志,日志量爆炸 -
AFTER DELETE:只读OLD.id、OLD.created_at,注意TEXT字段长度别超审计表限制
audit_log 表结构怎么设计才不翻车?
最常见错误是照搬源表结构,或滥用 JSON / LONGTEXT。审计表不是存档库,而是快速定位工具。
- 主键用
id BIGINT UNSIGNED AUTO_INCREMENT,别用UUID或复合主键映射 -
pk_value VARCHAR(255)存主键值(兼容UUID、数字、字符串型主键),别用BIGINT强转 -
changed_fields JSON只存差异字段,用JSON_OBJECT()构造,但必须包裹IF(OLD.col != NEW.col, ...)避免NULL报错 - 敏感字段如
password、phone必须在触发器里显式跳过,不会自动脱敏 - 引擎必须是
InnoDB,且不能加外键——删源表时会失败
如何安全获取操作人和时间?
CURRENT_USER() 不可靠,USER() 返回的是连接账号(如 app@10.0.1.5),不是业务系统里的用户 ID。真正可控的方式只有两种:
- 业务层每次执行前设会话变量:
SET @current_user_id = 12345;,触发器里用IFNULL(@current_user_id, -1) - 用
USER()+CONNECTION_ID()组合溯源,但连接池场景下USER()全是应用账号,此时必须依赖@app_name或@client_ip等预置变量 - 时间统一用
NOW(3),别用CURRENT_TIMESTAMP(复制场景下可能变成从库时间)或SYS_DATE()(语义模糊) - 确保执行触发器的数据库用户对
audit_log有INSERT权限——DBA 常只给业务表权限,忘了审计表
触发器里写 INSERT INTO audit_log 为什么没生效?
表面看 SQL 没报错,但日志就是空。核心原因往往藏得深:
- 没用
DELIMITER切换结束符,导致触发器定义被截断,实际没创建成功 - 审计表字段类型不匹配,比如
pk_value定义为BIGINT却试图插入UUID,触发器静默失败 - 用了
REPLACE INTO或INSERT ... ON DUPLICATE KEY UPDATE,唯一键冲突时中断流程,不报错也不写入 - 触发器里调用了含事务控制的存储过程,或用了
SELECT ... FOR UPDATE,直接报错Can't update table 'xxx' in stored function or trigger -
audit_log表用了MyISAM引擎——无法保证与主表事务一致性,且不支持行级锁
真正容易被忽略的是:所有写入必须用裸 INSERT INTO audit_log (...) VALUES (...),别包装、别封装、别加异常捕获——MySQL 触发器里没有 TRY/CATCH,出错即终止,且不会 rollback 主事务(除非你显式写了 ROLLBACK)。











