SQL Server嵌套TRY CATCH必须紧邻,BEGIN CATCH须紧跟END TRY,中间不可有GO、空行或语句;ERROR_PROCEDURE()返回最内层出错的存储过程名;CATCH中再出错需嵌套TRY CATCH捕获;XACT_ABORT OFF时CATCH内日志写入可能因事务状态失败。

嵌套TRY CATCH必须紧邻,不能被GO或空行隔开
SQL Server 要求 BEGIN CATCH 必须紧跟在 END TRY 后面,中间不能有任何语句、注释、GO 或空行。一旦被 GO 拆成两个批处理,就会报错:“'BEGIN CATCH' 附近有语法错误”。
常见踩坑点:
- 开发时习惯性加
GO分隔逻辑块,结果嵌套结构直接失效 - 复制粘贴代码时带入不可见空格或换行,表面看“紧邻”,实际解析失败
- 在
END TRY和BEGIN CATCH之间写了PRINT或SELECT—— 这属于“其他语句”,语法不合法
ERROR_PROCEDURE() 返回最内层出错的存储过程名
如果外层存储过程 usp_outer 调用内层 usp_inner,而 usp_inner 中发生错误,那么在 usp_outer 的 CATCH 块里调用 ERROR_PROCEDURE(),返回的是 usp_inner,不是 usp_outer。
这意味着:
- 日志记录时别假设
ERROR_PROCEDURE()就是当前正在执行的存储过程名 - 若需明确调用链,得靠传参(比如在调用
usp_inner前把当前过程名作为@caller参数传入) -
ERROR_LINE()返回的也是usp_inner内部出错的那一行,不是外层调用位置
CATCH块里再出错,没嵌套就直接抛给上层调用方
如果在 CATCH 块中执行日志写入(比如 INSERT INTO error_log),而这张表恰好被删了或权限不足,那这个新错误不会被当前 CATCH 捕获——除非你显式再套一层 BEGIN TRY…END TRY。
所以安全做法是:
- 在
CATCH块开头立即检查关键对象是否存在(如用IF OBJECT_ID('error_log') IS NOT NULL) - 对日志操作本身加嵌套
TRY CATCH,避免因日志失败导致整个异常处理崩溃 - 慎用
RAISERROR向外重抛错误:它不会自动继承原错误的ERROR_NUMBER(),需手动传参,否则调用方看到的错误号是 50000
事务状态不可控,XACT_ABORT OFF 时CATCH里可能无法提交日志
默认情况下 SET XACT_ABORT OFF,遇到错误后事务进入“不可提交”状态(XACT_STATE() = -1),此时即使你在 CATCH 里写 INSERT INTO error_log,也会失败并报错:“The current transaction cannot be committed and cannot support operations that write to the log file.”
解决路径只有两条:
- 在
TRY块开头加SET XACT_ABORT ON,让错误触发自动回滚,之后CATCH中可安全执行独立日志操作(前提是日志表不在同一事务上下文中) - 改用表变量暂存错误信息(
DECLARE @err TABLE(...)),等CATCH结束、事务彻底清理后再统一落库
真正麻烦的不是写法,而是很多人根本没意识到 CATCH 里的语句也可能因事务状态而静默失败——日志没写成,还误以为“已捕获”。











