RAISERROR 严重级别应设为11–19,推荐16;消息须用N前缀防乱码;需显式ROLLBACK TRANSACTION回滚事务;动态参数用%s/%d格式化,避免拼接;需结合业务逻辑判断重试、日志与事务边界。

RAISERROR 严重级别必须设为 11–19 才能被 C# 捕获
低于 11 的级别(比如 10)只是信息性消息,不会触发 C# 的 catch (SqlException ex);高于 19(比如 20)则直接断开连接,C# 收到的往往是“传输级错误”这类模糊提示,而非你写的原消息。所以实际可用且安全的范围只有 11 到 19,推荐统一用 16——它既进 catch,又不致命,还符合 SQL Server 默认用户错误的惯例。
消息内容要带 N 前缀,否则中文会乱码
如果用字符串字面量传消息,必须写成 N'用户名不能为空',不能是 '用户名不能为空'。SQL Server 把非 Unicode 字符串转成当前数据库排序规则编码,一旦涉及中文、emoji 或特殊符号,大概率变成问号或乱码。变量方式也一样:DECLARE @msg NVARCHAR(200) = N'订单号 ' + @orderNo + N' 已存在',再传给 RAISERROR。
RAISERROR 不会自动回滚事务,必须显式加 ROLLBACK TRANSACTION
这是最常踩的坑:只写 RAISERROR('校验失败', 16, 1),看起来报错,但 INSERT/UPDATE 还是提交了。尤其在触发器或嵌套存储过程中,事务状态不受影响。正确做法是:
- 先检查是否有活跃事务:
IF @@TRANCOUNT > 0 - 紧接着执行:
ROLLBACK TRANSACTION - 最后再
RAISERROR(顺序不能反,否则回滚前就抛错退出,后续语句不执行)
或者更稳妥地,在存储过程开头加 SET XACT_ABORT ON,再配合 THROW(SQL Server 2012+),它默认中断批处理并回滚当前事务。
动态参数要用 %s %d 格式,别硬拼字符串
拼接字符串容易被 SQL 注入或引号搞崩,比如 '用户 ' + @name + ' 删除失败' 遇到名字含单引号就语法错误。应该用格式化方式:
DECLARE @id INT = 123; RAISERROR(N'无法删除 ID 为 %d 的记录', 16, 1, @id);
支持多种类型:%s(字符串)、%d(整数)、%05d(宽度填充)、%.2f(小数位),和 C 的 printf 行为一致。注意参数个数、顺序、类型必须和占位符严格匹配,否则报错或输出异常。
真正难的不是怎么写 RAISERROR,而是想清楚——这个错误该不该让上层重试?要不要记日志?事务边界在哪?这些决定了 severity 选 11 还是 16,要不要加 WITH LOG,以及 ROLLBACK 放在哪儿。











