sql server存储过程必须用begin try…catch配合xact_abort on、xact_state()检查和throw重抛,才能实现可靠异常处理;仅靠@@error易漏错、事务不一致且无法定位错误。

SQL Server 存储过程中不加 BEGIN TRY 就谈不上真正可靠的异常处理——裸写 BEGIN TRANSACTION 后只靠 @@ERROR 检查,大概率漏错、部分提交、调用方无感知。
为什么单靠 @@ERROR 不够用
SQL Server 2000 时代常用 IF @@ERROR 0 GOTO ErrorHandler,但它有硬伤:
- 只捕获上一条语句的错误,中间插入注释、
PRINT或变量赋值会打断链路 - 严重级别 @@ERROR 变非零
- 事务状态可能已损坏,但
@@ERROR为 0,导致继续COMMIT,数据不一致 - 无法获取错误位置(行号)、过程名、完整消息,日志难定位
TRY…CATCH 必须配合事务控制逻辑
仅把业务 SQL 包进 BEGIN TRY 不代表事务安全。关键在:是否真正回滚了、是否只在该回滚时回滚、是否干扰外层事务。
- 必须用
IF @@TRANCOUNT > 0 AND XACT_STATE() 0 ROLLBACK——XACT_STATE()为 -1 表示事务不可提交,强制回滚;为 0 表示已自动终止,不能再ROLLBACK - 不能无条件
ROLLBACK:若存储过程被调用时外层已有事务(@@TRANCOUNT > 1),你回滚会连父事务一起干掉 - 推荐统一用本地标志位控制:
DECLARE @TranStarted BIT = 0; IF @@TRANCOUNT = 0 BEGIN BEGIN TRAN; SET @TranStarted = 1; END,后续只对@TranStarted = 1的情况COMMIT或ROLLBACK
SET XACT_ABORT ON 是防崩底牌
它不是可选项,是生产环境必须开启的开关。作用是:让大多数运行时错误(主键冲突、类型转换失败、死锁、超时)直接终止当前批处理,并将事务置为不可提交状态(XACT_STATE() = -1)。
- 不开它,某些错误(如违反约束)只会报错但不中断后续语句执行,
TRY…CATCH可能根本进不去 - 开了它,配合
IF XACT_STATE() 0 ROLLBACK,就能确保“出错即清理”,不用依赖每条语句后手动查@@ERROR - 注意:它不影响
COMMIT或ROLLBACK本身的执行,只影响错误传播行为
错误信息怎么传出去才不丢
CATCH 块里拿到的错误,默认不会返回给应用程序——它只在当前批处理内有效。必须显式抛出。
- 优先用
THROW(SQL Server 2012+):保留原始错误号、严重级、状态、行号,THROW;即可原样重抛 - 兼容老版本用
RAISERROR:需手动拼接ERROR_MESSAGE()、ERROR_SEVERITY()、ERROR_STATE(),且严重级不能 > 18(除非有 sysadmin 权限) - 禁止只写
SELECT '出错了'或静默吞掉错误——调用方看到的是“成功”,实际数据已损坏
最易被忽略的一点:嵌套存储过程中,THROW 或 RAISERROR 必须在 CATCH 最末尾执行,否则后续语句(比如日志记录)失败会导致错误被覆盖;而日志表写入本身也应包裹在独立 TRY…CATCH 中,避免因日志失败阻断主错误传播。










