错误日志必须显式insert到专用表且脱离主事务,用sp_getapplock加锁、独立schema、error_*函数首行存变量、参数json序列化、脱敏、进度日志、set xact_abort on、throw重抛。

错误日志必须显式 INSERT INTO 到专用表,否则重启后、会话断开后就彻底丢失——PRINT 和 RAISERROR 都不落盘。
为什么日志表 INSERT 必须脱离主事务
把 INSERT INTO ErrorLog 写在主 TRY/CATCH 的事务里,一旦业务逻辑回滚,这条日志也跟着消失。这不是“没记”,是“记了又删”。
- 用
sp_getapplock加锁,例如@Resource = 'ErrorLogInsert',防并发写冲突 -
INSERT操作单独执行,不在主事务中COMMIT或ROLLBACK - 日志表建议放在独立 schema(如
audit.ErrorLog),禁用触发器、外键、复杂索引 - 最后调用
sp_releaseapplock释放锁,避免锁残留
ERROR_*() 函数必须在 CATCH 第一行就存进变量
这些函数值只在当前 CATCH 块内有效,后续任何语句(包括 IF、SELECT、甚至另一个 TRY)都可能覆盖它们。
-
ERROR_NUMBER()比@@ERROR更准,能跨语句捕获 -
ERROR_PROCEDURE()返回实际出错的存储过程名,不是调用它的外层过程 -
ORIGINAL_LOGIN()取登录名,比SUSER_SNAME()在连接池下更可靠 -
SYSDATETIME()记时间,避免GETDATE()的时区偏差 - 消息含单引号时,先用
REPLACE(@msg, '''', '''''')转义,防 SQL 截断
日志字段不能只记错误号和消息
光有 ERROR_MESSAGE() 和 ERROR_NUMBER() 很难复现问题,尤其参数驱动的过程。
- 用
FOR JSON PATH把输入参数序列化为params_json字段 - 记录
@@ROWCOUNT,区分“真失败”和“静默失败”(比如UPDATE影响 0 行) - 敏感字段脱敏:手机号写成
LEFT(@phone, 3) + '****' + RIGHT(@phone, 4) - 循环类逻辑加进度标记,例如
IF @i % 50 = 0插一条中间日志,防卡死无感知
SET XACT_ABORT ON 是默认安全底线
不加这句,很多错误不会自动回滚事务,导致 CATCH 里尝试写日志时,事务已处于不可提交状态。
- 必须在
TRY块开头就执行SET XACT_ABORT ON - 重抛错误优先用
THROW,它保留原始错误号、行号、消息;RAISERROR会强制改成 50000 -
TRY/CATCH不捕获编译期错误(如表不存在、语法错)或严重级 ≤10 的警告,别指望它兜底一切 - 嵌套调用时,
ERROR_PROCEDURE()返回最内层出错过程名,不是入口过程
真正难的不是写那条 INSERT,而是保证它总能写进去——哪怕主逻辑崩了、事务回滚了、连接中断了,这条记录还得在。锁机制、表结构克制、字段来源可靠,缺一不可。










