能,但必须手写逻辑、分场景处理,且不能依赖触发器“自动审计”——它只提供执行时机,不提供审计语义;after insert 和 after update 是唯一安全选择,因 before 触发器日志可能与回滚数据不一致,after 才保证日志仅记录真实生效变更。

能,但必须手写逻辑、分场景处理,且不能依赖触发器“自动审计”——它只提供执行时机,不提供审计语义。
为什么 AFTER INSERT 和 AFTER UPDATE 是唯一安全选择
BEFORE 触发器里写审计日志,一旦后续 SQL 报错回滚,日志已落盘,数据和日志对不上。AFTER 才保证日志只记录真实生效的变更。
- AFTER INSERT:NEW 已确定,可安全读取所有字段值(包括自增 ID)
- AFTER UPDATE:OLD 和 NEW 都可用,但必须逐字段比对,否则无变更也记日志
- 千万别在 AFTER 触发器里再 UPDATE 或 DELETE 原表,MySQL 直接报
Can't update table 'xxx' in stored function/trigger
OLD 和 NEW 字段怎么取才不报错
INSERT 没有 OLD,UPDATE 和 DELETE 才有;DELETE 没有 NEW。硬写 OLD.id 在 INSERT 触发器里会崩,错误是 Unknown column 'OLD.id' in 'field list'。
- INSERT 场景只用
NEW.id、NEW.email等 - UPDATE 场景建议加判断:
IF OLD.email != NEW.email THEN ...,避免字段没变也写日志 - DELETE 场景只用
OLD.id、OLD.created_at,即使外键级联删了,OLD 仍可读 - JSON_OBJECT 要显式列字段:
JSON_OBJECT('id', NEW.id, 'name', NEW.name),不支持JSON_OBJECT(*)
audit_log 表结构设计最容易翻车的点
照搬原表结构建 audit_log 是最常见错误,比如用 CREATE TABLE audit_log LIKE users,结果主键、索引、默认值全抄过来,后续维护直接卡死。
-
pk_value必须是VARCHAR(255),不能照抄原表 INT 或 UUID 类型——复合主键或字符串主键就存不住 - 别给
changed_fields JSON加索引,MySQL 对 JSON 字段无法高效索引 - 时间字段用
DATETIME(3)+NOW(3),别用SYS_DATE()或模糊的CURRENT_TIMESTAMP - 敏感字段如
password、phone必须在触发器里显式排除,不会自动脱敏
USER() 和 CURRENT_USER() 到底该用哪个
用 USER()。它返回 'app_user@10.0.1.5' 这种格式,能区分连接来源;CURRENT_USER() 只返回授权用户(如 'app_user'@'%'),丢失客户端 IP 信息。
- 如果只要用户名,用
SUBSTRING_INDEX(USER(), '@', 1) - 如果要存 IP,得靠应用层传参(触发器本身拿不到客户端真实 IP)
- 别把
USER()当作应用账号——它反映的是数据库连接身份,不是业务系统里的 user_id
真正难的不是写几个 INSERT 语句,而是让每个触发器都扛住字段增减、类型变更、事务回滚、高并发写入这四关。漏判一个 OLD.col != NEW.col,日志表一天就能涨几十 GB。











