触发器中 insert into audit_log 未生效的主因是未指定触发时机(如after insert)或未用delimiter切换语句结束符;audit_log表应采用最小可用结构,含id、table_name、operation、record_id、old_data、new_data、user、created_at字段,优先使用json类型而非text,并避免在delete触发器中做逻辑删除。

触发器里写 INSERT INTO audit_log 为什么没生效?
常见原因是触发器执行时遇到权限不足或语法错误,但更隐蔽的问题是:MySQL 默认禁止在触发器中修改当前表(即不能对触发器所属表做 INSERT/UPDATE/DELETE),而 audit_log 是另一张表,这点没问题;真正卡住的是 AFTER INSERT 触发器里如果用了 SELECT ... FOR UPDATE 或调用含事务控制的存储过程,会直接报错 Can't update table 'xxx' in stored function or trigger。实操建议:
- 审计日志表
audit_log必须是 InnoDB 引擎,且字段设计要避开触发器内可能访问的源表字段名(比如源表有updated_at,audit_log 别也叫这个,避免NEW.updated_at冲突) - 所有写入用
INSERT INTO audit_log (...) VALUES (...),别用REPLACE或INSERT ... ON DUPLICATE KEY UPDATE,后者在触发器里容易因唯一键冲突中断流程 - 确保执行触发器的用户对
audit_log有INSERT权限——很多 DBA 只给了业务表权限,忘了审计表
INSERT/UPDATE/DELETE 三种操作怎么共用一套日志结构?
关键不是拼 SQL,而是统一提取变更上下文。NEW 和 OLD 的存在状态决定你能取什么值:INSERT 只有 NEW,DELETE 只有 OLD,UPDATE 两者都有。实操建议:
- 日志表至少包含:
table_name、operation(值为 'INSERT'/'UPDATE'/'DELETE')、record_id(主键值,从NEW.id或OLD.id取)、changed_fields(JSON 字符串,用JSON_OBJECT()构造,UPDATE 时只放变化字段) - 别在触发器里拼接完整旧/新记录 JSON——太慢,且 MySQL 5.7+ 的
JSON_OBJECT()不支持动态键名,得硬编码字段列表 - 如果源表有 BLOB/TEXT 字段,审计日志里别存全文,改存
LENGTH(field)或 MD5 哈希值,否则触发器延迟明显
触发器里如何安全获取操作人和时间?
MySQL 没有内置“当前登录用户”变量可直接用,USER() 返回的是连接账户(如 'app@10.0.1.5'),不是业务系统里的操作人 ID。实操建议:
- 强制业务层在每次写操作前设置会话变量:
SET @current_user_id = 12345;,触发器里用IFNULL(@current_user_id, -1)取值 - 时间戳统一用
NOW(),别用SYS_DATE()或CURRENT_TIMESTAMP——后者在某些版本里会被优化成语句开始时间,导致同事务内多条变更日志时间相同 - 如果用代理层(如 ProxySQL)或中间件,可在连接池层自动注入
@current_user_id,避免每个业务 SQL 都手动 SET
为什么上线后发现日志漏写或重复写?
根本原因是触发器不参与业务事务回滚——你执行一个事务,里面 INSERT 失败回滚了,但触发器已经把日志写进去了,造成“有日志无数据”。实操建议:
- 审计日志必须和业务数据在同一个物理实例、同一个数据库下,且用相同事务隔离级别(推荐
READ-COMMITTED),否则跨库触发器无法感知事务状态 - 别依赖触发器做关键业务逻辑(比如扣库存后写日志),它只是旁路记录;真要强一致性,得用应用层双写 + 最终一致性校验
- 上线前用
SHOW TRIGGERS LIKE 'orders'检查触发器定义是否被意外覆盖,特别是 ALTER TABLE 后某些 MySQL 版本会清空触发器
最麻烦的其实是长事务场景:一个 UPDATE 跑 30 秒,触发器里的 NOW() 记录的是触发时刻,但实际提交可能滞后,而审计需求常要求“提交时间”,这时只能放弃触发器,改用 binlog 解析方案。











