必须用 begin try + begin catch,单靠 xact_state() 无法捕获错误——它只反映事务状态,不触发错误转移;xact_state() 是“事后检查员”,非“警报器”,错误发生后控制流不中断,error_*() 函数值仅在 catch 首行可靠,且需 set xact_abort on 才能确保多数严重错误进入 catch。

必须用 BEGIN TRY + BEGIN CATCH,单靠 XACT_STATE() 无法捕获错误——它只反映事务状态,不触发错误转移。
为什么不能跳过 BEGIN TRY 直接查 XACT_STATE()
XACT_STATE() 是个“事后检查员”,不是“警报器”。它在任意时刻都能返回当前事务健康度(-1、0 或 1),但不会主动中断执行流。没有 BEGIN TRY,错误发生后语句继续跑,可能已部分写入数据,而你根本不知道哪条出错了。
- 错误发生时控制权不会自动跳转,
XACT_STATE()值可能还是1,直到下一条语句触发严重错误才变 - 无法拿到
ERROR_LINE()、ERROR_MESSAGE()这类上下文信息——它们只在CATCH块内有效 - 若错误是约束冲突(如主键重复),事务可能已进不可提交态(
XACT_STATE() = -1),但你没做ROLLBACK,后续还调了COMMIT,直接报错
XACT_STATE() 必须在 CATCH 块里立刻判断
进了 CATCH 不代表就能无脑 ROLLBACK。要先看 XACT_STATE(),否则可能对“无事务”或“已终止事务”执行回滚,报 The ROLLBACK TRANSACTION request has no corresponding BEGIN TRANSACTION。
-
XACT_STATE() = -1:事务已损坏,必须ROLLBACK TRANSACTION -
XACT_STATE() = 1:事务仍可提交,但业务逻辑通常要求回滚(比如插入失败后不希望更新生效) -
XACT_STATE() = 0:当前无活动事务(例如错误发生在SELECT阶段,还没开事务),跳过ROLLBACK - 检查必须在
CATCH第一行做,避免中间语句(如DECLARE或SET)覆盖事务状态
SET XACT_ABORT ON 是 TRY/CATCH 能靠得住的前提
不开 XACT_ABORT ON,很多运行时错误(如死锁、超时、类型转换失败)不会终止批处理,控制流不进 CATCH,XACT_STATE() 就永远没机会被检查。
- 开启后,多数严重错误(级别 ≥ 11)会立即将事务置为
XACT_STATE() = -1,并跳转到CATCH - 它不替代
TRY/CATCH,而是让TRY/CATCH真正生效的底牌 - 生产环境必须加,且建议放在存储过程开头,紧挨着
AS
事务嵌套时怎么安全回滚而不破坏外层事务
如果存储过程被另一个事务调用(@@TRANCOUNT > 1),你不能直接 ROLLBACK——那会把父事务也干掉。
- 用本地标志位记录是否由本过程开启事务:
DECLARE @TranStarted BIT = 0; IF @@TRANCOUNT = 0 BEGIN BEGIN TRAN; SET @TranStarted = 1; END - 在
CATCH中只对@TranStarted = 1的情况执行ROLLBACK - 成功路径下,也只在
@TranStarted = 1时COMMIT - 这样既保住了外层事务完整性,又确保自己开的事务干净收场
最容易被忽略的是:错误发生后,ERROR_*() 函数值只在 CATCH 块第一行可靠;多写一句 DECLARE 就可能丢掉行号和消息。别想着“等会儿再取”,得马上存进变量。










