必须用AFTER触发器且每个操作类型单独建一个,因BEFORE出错会导致主事务回滚、审计失败,而AFTER能确保主操作成功后日志落盘;AFTER UPDATE需加变更判断防日志爆炸,AFTER DELETE用稳定OLD值,AFTER INSERT用NEW值;audit_log表须用InnoDB引擎、固定字段结构、NOW(3)时间戳、显式列名及足够长度。

必须用 AFTER 触发器,且每个操作类型(INSERT/UPDATE/DELETE)单独建一个;BEFORE 容易让事务失败连带审计失败,AFTER 才能保证主操作成功后日志一定落盘。
为什么不能用 BEFORE UPDATE/DELETE 记审计日志
很多人图省事在 BEFORE UPDATE 里记录 OLD 和 NEW,但这是高危操作:
-
BEFORE阶段触发器出错(比如日志表字段太短、JSON序列化失败),整个业务事务直接回滚——审计没记成,主流程反而挂了 -
BEFORE DELETE虽然OLD可用,但 MySQL 8.0+ 在某些复制模式下可能因延迟导致OLD不稳定;而AFTER DELETE的OLD是最终态,更可靠 -
BEFORE INSERT根本没有OLD,且无法确保自增 ID 已分配完成,日志里的主键值可能不准
AFTER INSERT / UPDATE / DELETE 各自怎么写才安全
核心原则:只取必要字段、显式列名、避开大字段和 JSON 坑。
-
AFTER INSERT:直接用NEW.col1, NEW.col2,别写SELECT * FROM inserted(语法不合法,MySQL 禁止) -
AFTER UPDATE:必须加判断,例如IF OLD.name != NEW.name THEN ... END IF;,否则无变更也写日志,量爆炸 -
AFTER DELETE:只能用OLD.col1, OLD.col2,且字段列表要和日志表严格对齐;若原表有TEXT或BLOB,日志表对应字段也得是同类型,否则报Truncated incorrect DOUBLE value
audit_log 表结构和字段怎么设计才扛得住线上压力
别塞 JSON,别用 CURRENT_TIMESTAMP,别忽略连接上下文。
- 字段必须固定结构:
table_name(VARCHAR(64))、operation_type(ENUM('INSERT','UPDATE','DELETE'))、pk_value(字符串化主键,如CONCAT('id:', OLD.id))、changed_fields(逗号分隔的字段名,VARCHAR(255)) - 时间戳用
NOW(3),不是CURRENT_TIMESTAMP——前者是触发器执行时刻,后者可能固化在语句开始时,差几十毫秒就影响排查顺序 - 操作人必须用
USER()+CONNECTION_ID()组合;如果用了连接池,USER()全是应用账号,就得靠会话变量:SET @app_user = 'admin@10.0.1.5',触发器里读@app_user - 引擎必须是
InnoDB,否则无法和主表事务一致;别用MyISAM或MEMORY
最容易被忽略的三个细节
它们不出问题时完全隐形,一出就是线上事故:
- 日志表字段长度必须 ≥ 原表对应字段,尤其是
VARCHAR和TEXT类型,否则插入时静默截断,关键信息丢失 - 所有触发器里的时间函数统一用
NOW(3),且确保 MySQL 服务端、客户端、表字段都设为同一时区(推荐 UTC),否则跨机房或从库查日志时间乱序 - 批量操作(如
UPDATE t SET x=1 WHERE id IN (1,2,3,4,5))会触发 N 次触发器,但每条日志都是独立事务。如果日志表没建好索引,高峰期直接拖慢主表写入


















