try...catch仅捕获严重级别11–19的运行时错误,编译期错误(如表不存在、语法错误、变量赋值失败)在try执行前即终止批处理;raiserror级别

TRY...CATCH 为什么有些错误根本进不去 CATCH?
因为 TRY...CATCH 只捕获严重级别 11–19 的运行时错误,编译期错误(比如 SELECT * FROM NonExistentTable、语法写错、变量类型赋值失败)压根不会执行到 TRY 块里,直接报错退出批处理。
常见“假失效”场景:
-
RAISERROR('msg', 10, 1)不会触发 CATCH —— 严重级必须 ≥11 才行 -
DECLARE @x INT = 'abc'在编译阶段就失败,TRY 还没开始 - 动态 SQL(如
EXEC('SELECT * FROM ...'))能把部分编译期错误转为运行时报错,从而被 CATCH 捕获 - DDL 语句(
CREATE TABLE)出错可能直接中断批处理,CATCH 来不及响应
ERROR_*() 函数必须在 CATCH 第一行就调用
所有 ERROR_NUMBER()、ERROR_MESSAGE()、ERROR_LINE() 等函数只在当前 CATCH 块内有效,且只返回最近一次错误信息。一旦你在 CATCH 里执行了其他可能出错的操作(比如往日志表 INSERT),原始错误上下文就会被覆盖。
正确做法是立即存入变量:
DECLARE @ErrorNumber INT = ERROR_NUMBER(); DECLARE @ErrorMessage NVARCHAR(4000) = ERROR_MESSAGE(); DECLARE @ErrorLine INT = ERROR_LINE(); DECLARE @ErrorProcedure SYSNAME = ERROR_PROCEDURE();
注意:ERROR_MESSAGE() 里可能含单引号,拼接进 INSERT 语句前建议用 REPLACE(@ErrorMessage, '''', '''''') 防截断。
事务回滚不能靠“进了 CATCH 就自动 rollback”
默认情况下,约束冲突这类错误不会自动回滚事务——事务仍处于打开状态,但可能已标记为不可提交(XACT_STATE() = -1)。此时盲目 ROLLBACK TRANSACTION 可能失败,而只查 @@TRANCOUNT 也不可靠(它可能 > 0 但事务实际已失效)。
安全写法是先判断事务状态:
IF XACT_STATE() 0
ROLLBACK TRANSACTION;
更关键的是:务必在 TRY 块开头加 SET XACT_ABORT ON,否则多数错误不会终止当前批处理,CATCH 可能被绕过;但注意,SET XACT_ABORT ON 本身会让某些错误跳过 CATCH 直接退出——所以它和 TRY...CATCH 是互斥增强关系,不是简单开关。
重抛错误该用 THROW 还是 RAISERROR?
SQL Server 2012+ 强烈推荐用 THROW,而不是 RAISERROR。
THROW(无参数)能原样保留原始错误号、消息、行号和状态;而 RAISERROR 必须手动传参,且强制把错误号改成 50000,丢失关键定位信息。
示例:
BEGIN CATCH
-- 先保存上下文
DECLARE @ErrorNumber INT = ERROR_NUMBER();
DECLARE @ErrorMessage NVARCHAR(4000) = ERROR_MESSAGE();
-- ……记录日志逻辑……
THROW; -- 原样抛给上层,不丢上下文
END CATCH
如果要在日志里记录后再向上抛,别用 RAISERROR(@ErrorMessage, @ErrorSeverity, @ErrorState) —— 它改写了错误号,调试时你会找不到原始错误来源。











