SQL Server 的 ROLLBACK TRANSACTION 总是全盘回滚,因其无嵌套事务机制,仅靠 @@TRANCOUNT 计数,ROLLBACK 会归零该计数并撤销所有未提交更改;SAVE TRANSACTION 仅设保存点,需在活跃事务中使用且不可跨过程。

SQL Server 存储过程中写 ROLLBACK TRANSACTION 不等于“回滚上一步”,它会直接清空整个事务栈、归零 @@TRANCOUNT,连前面成功的操作也一并撤销——除非你用 SAVE TRANSACTION + ROLLBACK TO 显式控制范围。
为什么 ROLLBACK TRANSACTION 总是全盘回滚
SQL Server 没有真正嵌套事务,BEGIN TRANSACTION 只是递增 @@TRANCOUNT,而 ROLLBACK TRANSACTION 无条件归零该计数器并撤回所有未提交更改。哪怕你在 TRY 块里嵌了三层 BEGIN,一个 ROLLBACK 就全部消失。
- 典型报错:
Msg 266, Level 16, State 2: Transaction count after EXECUTE indicates a mismatch,多见于子过程擅自BEGIN/ROLLBACK -
@@ERROR不可靠:只保留上一条语句的错误号,中间插一句SELECT或变量赋值就覆盖掉了 - 真正该依赖的是
XACT_STATE()和@@TRANCOUNT:CATCH 中先判断IF @@TRANCOUNT > 0再ROLLBACK TO,否则可能触发Msg 3903(没有对应事务)
SAVE TRANSACTION 必须在事务中执行,且不能跨过程边界
SAVE TRANSACTION 不开启新事务,只是在当前事务内打一个可回退标记点。它要求调用前已存在活跃事务(即 @@TRANCOUNT > 0),否则直接报 Msg 628: Cannot issue SAVE TRANSACTION when there is no active transaction。
- 保存点名必须是字面量(如
savepoint_orderdetails),不能是变量@sp_name;若需动态命名,得用EXEC sp_executesql拼接,但要严防 SQL 注入 - 同名保存点会被后一次覆盖(不报错),建议带业务上下文,比如
sp_after_user_insert - 保存点无法跨存储过程调用:子过程里设的
SAVE TRANSACTION,父过程的ROLLBACK TO找不到它;局部回滚必须由调用方在调用前设点
子过程不该自己 BEGIN/COMMIT/ROLLBACK
被调用的存储过程如果自行管理事务,会破坏外层事务一致性。SQL Server 不支持自治事务,子过程里的 BEGIN TRANSACTION 只是增加计数,ROLLBACK 则直接终结整个调用链。
- 正确做法:子过程只做数据操作,通过
RETURN值或OUTPUT参数通知调用方是否失败 - 调用方统一控制事务边界:在调用前
BEGIN TRANSACTION,出错时ROLLBACK TO自己设的保存点 - 务必开启
SET XACT_ABORT ON:它能让主键冲突、类型转换失败等运行时错误自动中止事务,避免 TRY...CATCH 漏捕获非严重错误
最易被忽略的一点:回滚到保存点后,事务仍处于活跃状态(@@TRANCOUNT 不变),锁可能未释放,数据仍不可见或阻塞其他会话——你必须显式 COMMIT 或 ROLLBACK 才算真正收尾。

















