日志写入会被外层事务回滚,因sql server事务是连接级的;可靠方案有三:用linked server新建连接、service broker异步投递、应用层独立连接写日志。

直接用 BEGIN TRY...BEGIN CATCH 捕获错误并写入日志表是可行的,但**日志写入本身会被外层事务回滚掉**——这是最常被忽略、导致“明明写了日志却查不到”的根本原因。
为什么 INSERT 日志语句总在回滚后消失?
SQL Server 的事务是连接级(session-level)的,所有操作共享同一个事务上下文。哪怕你在 BEGIN CATCH 里执行 INSERT INTO ErrorLog,只要外层调用者显式开启了事务且最终执行了 ROLLBACK,这条日志也会被一并撤销。
- 常见场景:应用层开启事务 → 调用存储过程 → 过程内出错 →
BEGIN CATCH写日志 → 应用层捕获异常后回滚整个事务 → 日志记录丢失 -
SAVE TRAN或子事务(BEGIN TRAN嵌套)无法解决:SQL Server 不支持真正独立提交的子事务 - 动态 SQL(
EXEC('INSERT...'))同样无效:仍在同一连接和事务中
真正能落地的三种日志写入方式
绕过事务绑定,核心思路是「脱离当前会话的事务上下文」:
-
方案一:用 Linked Server 调用本地数据库(最轻量)
配置一个指向本机实例的链接服务器(如
[LOCAL]),然后在CATCH中执行:EXEC [LOCAL].[YourDB].dbo.usp_WriteErrorLog @params;→ 链接服务器调用会建立新连接,事务独立提交 - 方案二:用 Service Broker 异步投递 业务逻辑发送一条消息到队列,由激活的存储过程异步写日志;完全解耦、不阻塞主流程,适合高并发场景
-
方案三:由应用层统一写日志
存储过程只用
THROW重新抛出错误(SQL Server 2012+),让应用捕获后通过独立数据库连接写日志表;职责清晰,运维友好
必须检查的 CATCH 内部细节
即使解决了事务问题,日志内容是否完整、可用,取决于你调用的错误函数是否在正确时机被读取:
-
ERROR_NUMBER()、ERROR_MESSAGE()等函数**只在BEGIN CATCH块内有效**;一旦离开(比如调用另一个存储过程),值就变成NULL - 不要在
CATCH里再执行可能出错的语句(如未检查的SELECT),否则会覆盖原始错误上下文 - 务必加
IF XACT_STATE() = -1 RETURN判断:若事务已进入不可提交状态(XACT_STATE() = -1),强行写日志会失败并报错 - 示例安全写法:
IF XACT_STATE() 0<br> INSERT INTO ErrorLog (...) VALUES (...);
事务隔离是硬约束,不是编码习惯问题。想让日志“写进去就真在”,必须跳出当前连接的事务边界——选 Linked Server 是最快验证路径,但长期看,Service Broker 或应用层接管更利于可观测性建设。











