TRY-CATCH只捕获运行时错误(如除零、主键冲突),不捕获编译期错误(如语法错、对象不存在);DDL语句出错可能中断批处理,CATCH来不及触发;RAISERROR需≥11级才被捕获;CATCH中须立即用ERROR_*函数保存错误信息;事务回滚前须检查XACT_STATE()。

SQL Server 里 TRY-CATCH 不生效?检查是否在批处理顶层
TRY-CATCH 在 SQL Server 中只捕获运行时错误(如除零、主键冲突),但不捕获编译期错误(如语法错、对象不存在)——这类错误根本不会进入 TRY 块。最常见误用是把 TRY-CATCH 写在存储过程内部却忘了它只对当前批处理有效。
- 确保
TRY块从批处理开头开始,不要嵌套在IF或循环里再起BEGIN TRY - DDL 语句(如
CREATE TABLE)若出错,可能直接中断批处理,CATCH来不及触发;建议先用OBJECT_ID()检查对象是否存在 -
RAISERROR的严重级别必须 ≥ 11 才能被CATCH捕获;级别 10 及以下是“信息性消息”,会直接输出并继续执行
如何在 CATCH 块里拿到完整错误信息
仅靠 ERROR_MESSAGE() 往往不够,比如主键冲突时提示模糊,你得结合上下文定位问题。SQL Server 提供一组 ERROR_* 函数,必须在 CATCH 块第一行就保存,否则后续语句可能覆盖它们的值。
- 立刻存入变量:
DECLARE @msg NVARCHAR(4000) = ERROR_MESSAGE(); DECLARE @sev INT = ERROR_SEVERITY(); -
ERROR_LINE()返回出错语句所在行号,但注意:它指代的是TRY块内语句的行号,不是整个存储过程文件的行号 - 日志记录时建议拼接:
CONCAT('Proc:', OBJECT_NAME(@@PROCID), '; Line:', ERROR_LINE(), '; Msg:', ERROR_MESSAGE())
事务在 TRY-CATCH 中没回滚?检查 XACT_STATE()
很多人以为进了 CATCH 就自动可以 ROLLBACK,结果报错 The ROLLBACK TRANSACTION request has no corresponding BEGIN TRANSACTION。根本原因是:某些错误(如约束冲突)会让事务进入“不可提交”状态,但事务本身未必已显式开启。
- 执行
ROLLBACK前务必检查:IF XACT_STATE() 0 ROLLBACK TRANSACTION; -
XACT_STATE()返回 -1 表示事务已损坏(只能回滚),1 表示可提交,0 表示无活动事务 - 如果存储过程开头用了
SET XACT_ABORT ON,多数运行时错误会自动终止事务并回滚,但别依赖它替代显式控制
跨数据库或链接服务器调用时 TRY-CATCH 失效
通过 EXEC(@sql) AT [LinkedServer] 或四部分命名(如 [RemoteDB]..[Table])触发的远程错误,本地 CATCH 基本捕获不到——错误发生在远端,只返回一个泛化 OLE DB provider error。
- 远程操作尽量封装成远端存储过程,由远端自己处理异常并返回结构化结果码
- 本地可用
@@ERROR判断上一条语句是否失败(虽不如TRY-CATCH全面,但对远程调用更可靠) - 避免在
TRY块中混合本地事务和远程调用;一旦远程失败,本地事务状态极难预测
真正麻烦的从来不是写不写 TRY-CATCH,而是搞清它在哪一层失效、为什么没进 CATCH、以及事务到底处于什么状态——这些细节不验证,日志里只会看到一行空荡荡的 ERROR_MESSAGE()。

















