SQL 触发器日志与审计实现技巧

2次阅读

异步写日志(如redis队列+独立消费者),同步写则精简audit_log表结构、禁用非必要索引、避免触发器中查表或调用不支持函数;pg需防递归触发,sql server须正确处理inserted/deleted集合,oracle宜用after statement批量审计。

SQL 触发器日志与审计实现技巧

触发器里怎么安全写日志表,不卡主业务

直接写 INSERT INTO audit_log 很容易拖慢原事务,尤其在高并发更新场景下。根本原因是日志写入和业务更新绑在同一个事务里,锁表、磁盘 I/O、索引维护全得等它做完。

实操建议:

  • 用异步方式剥离日志:mysql 5.7+ 可考虑 INSERT DELAYED(仅 MyISAM 支持,已弃用),更稳妥的是把日志先写进内存队列(如 Redis List),再由独立消费者落库
  • 如果必须同步写,确保 audit_log 表是 InnoDB,且只有必要字段(idtable_nameoperationold_datanew_datacreated_at),禁用所有非必需索引,created_at 建普通索引即可
  • 避免在触发器里调用函数或子查询查其他表——select 会加读锁,可能引发死锁;也不要用 UUID()NOW() 外部函数,部分 MySQL 版本不支持

postgresql 中触发器审计如何避免 stack depth limit exceeded

递归触发是 PostgreSQL 审计最常踩的坑:比如在 UPDATE 触发器里又去 UPDATE audit_log,而 audit_log 上也有触发器,就可能无限嵌套。

实操建议:

  • pg_trigger_depth() 在触发器函数开头做守卫:if pg_trigger_depth() > 1 THEN RETURN NEW; END IF;
  • 不要给 audit_log 表本身定义任何触发器——它只存日志,不参与业务逻辑
  • tg_optg_table_name 区分操作类型,避免在 BEFORE 触发器里修改 NEW 字段后又触发同表另一个触发器

SQL Server 触发器审计时,inserteddeleted 表怎么取变更字段值

这两个临时表不是“一行一列”,而是集合,直接 SELECT * FROM inserted 在多行更新时会出错;另外,textntextimage 类型在新版本已废弃,但旧系统迁移时仍可能遇到无法读取问题。

实操建议:

  • 永远用 JOINCROSS APPLY 关联主表,而不是假设单行:SELECT i.id, i.name, d.name AS old_name FROM inserted i FULL OUTER JOIN deleted d ON i.id = d.id
  • 对大字段(如 varchar(max))做长度截断再存日志:LEFT(i.content, 2000),避免日志表膨胀过快
  • 注意 deleted 表在 INSERT 触发器中为空,inserted 表在 DELETE 触发器中为空——没判空直接 SELECT 会返回空结果集,但不会报错,容易漏审计

Oracle 的 AFTER STATEMENT 触发器为什么比 AFTER EACH ROW 更适合批量审计

AFTER EACH ROW 每改一行就触发一次,1000 行更新 = 1000 次日志插入,IO 压力翻倍;而 AFTER STATEMENT 整个语句执行完才触发一次,配合 DBMS_LOGMNRFLASHBACK_TRANSACTION_QUERY 才能拿到完整变更集。

实操建议:

  • 优先用 AFTER STATEMENT + INSERT /*+ append */ 批量写日志,减少 redo 日志压力
  • 别依赖 :OLD:NEW 绑定变量——它们在 statement 级触发器里不可用;改用 ORA_ROWSCNSCN_TO_TIMESTAMP(ORA_ROWSCN) 标记时间点
  • 如果要记录具体字段变化,得自己在应用层或通过 LogMiner 解析 redo,触发器本身做不到细粒度字段级差异捕获

真正难的不是写触发器,而是想清楚“谁需要看这条日志”“保留多久”“查的时候要不要反向还原”。很多团队了半年审计日志,结果发现没人真去查,或者查一次要跑十分钟——这时候删表比修触发器更有效。

text=ZqhQzanResources