触发器中insert into audit_log未生效的主因是未指定触发时机(如after insert)或未声明for each row;mysql需用delimiter切换结束符,postgresql函数须返回new/old,sql server须正确处理inserted/deleted表。

触发器里写 INSERT INTO audit_log 为什么没生效?
常见原因是触发器定义时没加 AFTER 或 FOR EACH ROW,导致逻辑根本没执行。MySQL 和 PostgreSQL 都要求显式声明触发时机和作用粒度,SQL Server 则必须用 AFTER INSERT, UPDATE, DELETE 明确事件类型。
实操建议:
- MySQL 必须写
CREATE TRIGGER ... AFTER INSERT ON target_table FOR EACH ROW INSERT INTO audit_log ... - PostgreSQL 触发器函数需返回
NEW(INSERT/UPDATE)或OLD(DELETE),否则语句会被拒绝 - SQL Server 中触发器内不能用
SELECT * FROM inserted后直接 INSERT,要先用变量或 CTE 提取字段,否则可能因多行操作报错 - 所有数据库里,
audit_log表的字段类型必须兼容源表对应列,比如源表user_name VARCHAR(50),日志表也得是VARCHAR(50)或更宽,否则插入失败且不报明显错误
如何记录变更前后的值(比如 UPDATE 场景)?
只记新值没意义,审计关键在“差值”。不同数据库暴露旧/新数据的方式不同,不能硬套同一套 SQL。
实操建议:
- MySQL:在
AFTER UPDATE触发器中用OLD.column_name和NEW.column_name直接引用 - PostgreSQL:触发器函数里通过
OLD.*和NEW.*访问,但注意 JSON 转换要用row_to_json(OLD),别用字符串拼接 - SQL Server:
inserted表存新值,deleted表存旧值,JOIN 时必须用主键关联,否则多行更新会笛卡尔积 - 避免在触发器里调用 UDF 或远程服务——延迟高、事务卡死风险大;真要序列化变更,用
JSON_OBJECT(MySQL 5.7+)、to_jsonb(OLD)(PG)这类内置函数更稳
audit_log 表设计容易被忽略的三个坑
日志表不是越全越好,字段设计不当会导致写入慢、查不出东西、甚至拖垮主业务表。
实操建议:
- 主键别用自增
INT,高并发下容易争抢;改用BIGINT或 UUID(PostgreSQL 推荐gen_random_uuid()) - 必须有
operated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,别依赖应用层传时间——触发器执行时刻才真实反映操作发生时间 - 别把
OLD_DATA和NEW_DATA设成 TEXT 或 JSON 类型后就不管了;加个INDEX在table_name+operated_at上,否则按表名查日志会全表扫 - 如果业务表有
TEXT或BLOB字段,审计日志里别原样存——截断到 1024 字符,或只存MD5哈希值,否则日志表体积爆炸
触发器禁用或绕过时审计就失效,怎么防?
开发人员 DISABLE TRIGGER、DBA 手动 INSERT 绕过、ETL 工具批量导入跳过触发器……这些场景下日志必然缺失,但往往没人发现。
实操建议:
- MySQL 8.0+ 可设
trigger_enabled = OFF全局参数禁止禁用,但需 DBA 权限;更实际的是在audit_log表加source VARCHAR(20)字段,触发器里填'TRIGGER',应用层写日志则填'APP',定期比对两路数据量 - PostgreSQL 用
pg_trigger视图监控触发器状态,写个每日巡检脚本查tgenabled != 'O' - SQL Server 的
sys.triggers表可查is_disabled,结合 SQL Agent 定时告警 - 最硬核的兜底:在主表上设
CHECK CONSTRAINT强制某些字段非空(比如updated_by),让绕过触发器的写入直接失败——代价是牺牲部分灵活性
审计不是加个触发器就完事,它本质是数据变更的“副产物”,一旦主流程出问题,日志大概率也残缺。真正可靠的方案,得把触发器当成其中一环,再叠一层应用层埋点或 CDC 工具做交叉验证。











