必须在存储过程内部显式写日志,而非依赖触发器或原生审计;因触发器仅响应DML语句、无法关联调用来源,原生审计缺失proc_name、参数等关键上下文,且日志需脱离主事务独立落库以避免回滚丢失或性能拖累。

为什么不能靠触发器捕获存储过程的数据修改
触发器响应的是 INSERT/UPDATE/DELETE 语句,不是存储过程调用。常见错误现象包括:
- 给
Orders表建了AFTER UPDATE触发器,但日志里查不到是usp_CreateOrder还是usp_ImportBatch发起的更新 - 多个存储过程共用同一张业务表,触发器日志混杂,无法归属到具体过程
- 触发器内再写日志,可能因嵌套导致死锁或递归插入
在存储过程里手动写审计日志的关键实操点
每个需要审计的存储过程,在关键逻辑前后主动插入一条记录到 audit_log 表。这不是“拦截”,是“声明式上报”。必须注意:
-
audit_log表至少含:proc_name(硬编码字符串更稳)、executed_by(用ORIGINAL_LOGIN(),不用CURRENT_USER())、start_time/end_time(用SYSDATETIME()赋值给变量)、params_json(用CONCAT拼接,如CONCAT('@p1=', ISNULL(CAST(@p1 AS VARCHAR(100)), 'NULL')))、row_count(@@ROWCOUNT)、error_message(ERROR_MESSAGE()) - 不要在主事务块里直接
INSERT INTO audit_log—— 主事务回滚会把日志也干掉;改用EXEC sp_executesql+SET XACT_ABORT OFF,或写入带WITH (TABLOCK)的独立日志表 - 避免循环体内高频写日志;如需逐行记录,先缓存到
#audit_temp表,最后批量INSERT
MySQL 和 PostgreSQL 的特殊处理
不同数据库对“事务内写日志”限制不同,绕过方式各异:
- MySQL 报错
ERROR 1442 (HY000): Can't update table 'audit_log' in stored function/trigger,本质是禁止函数/触发器修改正在被主逻辑操作的表。解法:用INSERT IGNORE INTO audit_log(需唯一约束),或更稳妥的INSERT ... ON DUPLICATE KEY UPDATE ignored = VALUES(ignored) - PostgreSQL 默认所有函数都在当前事务中运行。必须建
UNLOGGED审计表(不写 WAL,性能高),并定义VOLATILE函数包裹INSERT,加EXCEPTION WHEN OTHERS THEN NULL忽略失败
audit_log 表结构与落库时机容易被忽略
字段精简但不可少:id、proc_name、executed_by、executed_at、params_json;时间戳统一用变量赋值,别每条都调 SYSDATETIME()——高并发下毫秒级重复率会上升。真正难的是落库时机:日志必须脱离主事务控制,否则要么丢,要么拖垮业务。哪怕只是加一行 SET XACT_ABORT OFF 再套个 BEGIN TRY,也比裸插强得多。

















