必须用after触发器,因before阶段变更未发生且事务可能回滚,导致日志与数据不一致;after可稳定获取真实落库状态,且字段可用性完整。

必须用 AFTER 触发器,否则日志和数据必然不一致——这是所有翻车案例的共同起点。
为什么 BEFORE 触发器不能做审计
BEFORE 阶段变更还没发生,事务随时可能回滚,但日志已写入,导致审计表里记了一堆“没发生的事”。更直接的问题是字段不可用:BEFORE INSERT 读 OLD.id 报 Unknown column 'OLD.id' in 'field list';BEFORE DELETE 读 NEW.name 同样崩。只有 AFTER 能稳定拿到真实落库后的状态。
-
AFTER INSERT:NEW.id已分配完成(包括自增ID),可安全取值 -
AFTER UPDATE:OLD.col和NEW.col都可用,且值就是最终提交结果 -
AFTER DELETE:OLD是唯一能拿到被删行完整数据的时机
AFTER UPDATE 触发器里怎么避免日志爆炸
无脑记录所有字段,哪怕只改了一个 status,也会把整行(含大文本、JSON)全塞进日志表,很快撑爆 LONGTEXT 或触发截断警告。关键不是“记什么”,而是“只记变的”。
- 必须显式比对:
IF OLD.status != NEW.status THEN ... END IF; - 跳过自动更新字段(如
updated_at),否则每次访问都触发日志 - 敏感字段(如
password、id_card)在触发器里直接NULL掩码,别依赖应用层过滤 - 用
JSON_OBJECT('status', OLD.status, 'email', OLD.email)替代CONCAT(),避免NULL导致整条结果为NULL
审计表结构设计最容易踩的三个坑
照搬原表建 audit_log 是新手第一大雷——主键、外键、索引全抄过来,后续改业务表结构时直接卡死。
-
pk_value必须是VARCHAR(255),不能照抄原表INT或UUID类型,否则复合主键存不下 - 时间字段用
NOW(3),不是CURRENT_TIMESTAMP——后者可能固化在语句开始时刻,毫秒级偏差影响排查顺序 - 别给
changed_fields(哪怕存 JSON)加索引,MySQL 对 JSON 字段无法高效索引变更内容 - 引擎必须是
InnoDB,否则跨引擎事务失败;禁用外键,否则删业务表时审计表拦路
如何可靠获取操作人信息
USER() 返回的是 'app_user@10.20.30.40',但生产环境走代理或连接池后,IP 段全是中间件地址,账号也常复用。靠它区分具体操作人基本不可行。
- 首选方案:应用层执行业务 SQL 前先
SET @audit_user = 'alice',触发器里读@audit_user - 次选方案:用
SUBSTRING_INDEX(USER(), '@', 1)截用户名,但需 DBA 强制规范账号命名(如team-dev-alice) - 绝对避开
CURRENT_USER()——它返回权限校验后的账号(如'app_user'@'%'),丢失主机信息 - 如果必须记 IP,
USER()中的 host 部分可能被 DNS 缓存污染,建议配合@@hostname一起存
真正难的不是写几个触发器,而是让它们在线上扛住批量更新、高并发写入、字段类型突变这三类场景——这些地方不出问题时完全隐形,一出就是审计日志大面积缺失或主业务被拖慢。











