只有 severity 参数设为 11 到 19 之间,RAISERROR 抛出的错误才会被 TRY...CATCH 捕获;设成 10 或更低仅输出信息、不中断执行,CATCH 不触发。

RAISERROR 严重级别必须 ≥11 才能进 CATCH
直接说结论:只有 severity 参数设为 11 到 19 之间,RAISERROR 抛出的错误才会被 TRY...CATCH 捕获;设成 10 或更低,它只是打印消息,后续语句照常执行,CATCH 块完全不会触发。
常见错误现象:
-
RAISERROR(N'参数为空', 10, 1)—— SSMS 里能看到输出,但事务没中断、CATCH不执行、业务逻辑继续往下走 - 在存储过程中写了
CATCH块却始终不进,十有八九是severity填错了
推荐做法:
- 业务类自定义错误统一用
16(标准用户错误级别) - 避免用
20–25:这些是致命级,会直接断开连接,C# 端收到的往往是“传输级错误”,原始错误信息丢失 - 不要依赖
@@ERROR判断——它只反映上一条语句的错误号,且在RAISERROR后可能被覆盖;改用ERROR_NUMBER()更可靠
msg_str 必须加 N 前缀,否则中文乱码或截断
SQL Server 的 msg_str 参数类型是 nvarchar,如果传入普通单引号字符串(如 '用户不存在'),会被当作 varchar 隐式转换,中文极大概率变成问号或空格。
正确写法:
-
RAISERROR(N'用户 %s 不存在', 16, 1, @username)✅ -
RAISERROR('用户 %s 不存在', 16, 1, @username)❌(乱码风险高)
其他注意点:
- 消息总长上限为
2047字符,超长时只显示前2044字符 + 省略号 - 格式占位符(
%s、%d)个数必须与后续参数严格一致,否则报错Msg 10005, Level 15 - 别在
msg_str里拼接变量(如N'用户 ' + @username + N' 不存在'),既不安全也易出错;用参数化方式传参
RAISERROR 不自动回滚事务,必须显式 ROLLBACK
RAISERROR 只负责“抛错”,不负责“撤回”。哪怕 severity=16 触发了 CATCH,事务状态仍是“已开启未提交”,若 CATCH 块里没写 ROLLBACK TRANSACTION,上层调用者仍可能成功提交脏数据。
典型问题场景:
- 触发器中校验失败,
RAISERROR报错后,INSERT 却已落库 -
CATCH块里只记录日志、没ROLLBACK,导致事务挂起,后续查询被阻塞
安全写法要点:
- 在
CATCH块开头就检查IF @@TRANCOUNT > 0,再执行ROLLBACK TRANSACTION - 避免嵌套
TRY...CATCH中遗漏ROLLBACK—— 每一层CATCH都应处理事务状态 - 更省心的做法:SQL Server 2012+ 直接用
THROW,它默认终止批处理,配合SET XACT_ABORT ON能更可靠中断事务
RAISERROR vs THROW:CATCH 中重抛该选谁
如果要在 CATCH 块里把错误“原样传出去”,THROW 是更优解。它能完整保留原始错误号、消息内容、行号和过程名;而 RAISERROR 重抛时必须手动传参,且错误号强制变成 50000,上下文全丢。
示例对比:
-
THROW;—— 简洁,原样透出,推荐用于大多数重抛场景 -
RAISERROR(ERROR_MESSAGE(), ERROR_SEVERITY(), ERROR_STATE());—— 冗长,且ERROR_SEVERITY()返回的是原始值,但RAISERROR本身又不保证行为一致性
限制也要清楚:
-
THROW硬编码severity=16,不能自定义级别(比如你真需要18级做调试标记,只能用RAISERROR) -
THROW不能用于msg_id方式引用sys.messages中的预定义消息(Azure SQL 也不支持sp_addmessage)
真正容易被忽略的点是:很多人以为进了 CATCH 就万事大吉,其实事务状态、错误上下文、重抛方式这三件事没配对好,照样会漏数据、丢线索、难定位。











