触发器错误日志必须独立于主业务表,需建专用audit_error_log表置于audit_db库、物理隔离磁盘,字段含id、trigger_name、error_code、error_message、sql_state、occurred_at,引擎用innodb或heap+wal,加索引(trigger_name, occurred_at),sql server须用try...catch捕获并落盘,mysql需适配binlog_format=row,postgresql应结合raise log与自治事务兜底。

触发器错误日志必须独立于主业务表
触发器里出错,如果日志写进同一库甚至同表,轻则写不进去,重则引发死锁或事务回滚。主操作失败不是最坏结果——更糟的是错误静默消失,你根本不知道触发器早就不工作了。
必须建一张专用错误日志表,且满足:
-
audit_error_log表单独放在audit_db库下,磁盘路径与业务库物理隔离 - 字段至少含:
id(自增)、trigger_name、error_code、error_message、sql_state、occurred_at(用NOW(3)或CURRENT_TIMESTAMP(3)) - 引擎强制用
InnoDB(MySQL)或heap+WAL安全配置(PostgreSQL),禁用MyISAM - 加索引:
INDEX (trigger_name, occurred_at),避免查某触发器报错时全表扫
SQL Server 用 TRY...CATCH 捕获并落盘
直接 RAISERROR 或没处理的异常会把整个 INSERT 给拖垮。必须用 TRY...CATCH 包住日志写入逻辑,并确保即使 INSERT INTO audit_error_log 失败也不再抛出。
关键点:
-
CATCH块里只做两件事:拼错误信息、写日志表;不要THROW或RAISERROR再抛异常 - 用
ERROR_NUMBER()、ERROR_MESSAGE()、ERROR_LINE()获取上下文,别只记 “something went wrong” - 如果
audit_error_log插入也失败(比如权限不足或磁盘满),可退化为写 Windows Event Log(sp_trace_generateevent)或临时表,但不能丢弃
示例片段:
INSERT INTO audit_db.dbo.audit_error_log (trigger_name, error_code, error_message, occurred_at) VALUES (@trigger_name, ERROR_NUMBER(), ERROR_MESSAGE(), GETDATE());
MySQL 触发器里写日志要绕开 binlog_format 陷阱
MySQL 5.7+ 默认 binlog_format = STATEMENT 时,触发器内 INSERT INTO 日志表可能失败,报错类似 Can't update table 'audit_error_log' in stored function/trigger——这不是权限问题,是复制格式限制。
解决方法只有两个:
- 把
binlog_format改成ROW(推荐,兼容性好,且能准确捕获行变更) - 若无法改全局配置,就在触发器开头加判断:
IF @@binlog_format = 'STATEMENT' THEN INSERT ...; END IF;,但不如直接切ROW - 注意:哪怕只是
SELECT查日志表状态,也要避免在触发器里做,否则可能触发SELECT ... FOR UPDATE锁等待
PostgreSQL 需用 RAISE LOG + 自治事务兜底
RAISE NOTICE 在 psql 里默认不显示,RAISE WARNING 又太吵,真正可靠的错误日志得靠 RAISE LOG ——它写进服务端日志文件(路径由 log_directory 和 log_filename 控制),且不依赖客户端设置。
但有个硬伤:RAISE LOG 不保证事务提交后才落盘,万一主事务回滚,这条日志可能被丢。所以生产环境必须配自治事务(autonomous transaction):
- 用
dblink扩展发起一次独立连接写日志表(需提前创建 dblink 连接) - 或用
pg_background异步执行日志插入(避免阻塞) - 禁止在
RAISE LOG后再做任何 DML,否则容易因 WAL 冲突导致主事务卡住
容易忽略的一点:PostgreSQL 的 RAISE LOG 默认只对 superuser 可见,普通用户触发器要用 SECURITY DEFINER + 显式授权才能写成功。










