SQL Server 存储过程中默认不自动回滚全部语句,需显式使用SET XACT_ABORT ON或TRY...CATCH确保事务一致性;前者轻量适用于线性逻辑,后者支持自定义错误处理。

SQL Server 存储过程中不加显式控制,事务不会自动回滚全部语句——哪怕某条 INSERT 报错,后续的 UPDATE 仍可能执行成功。
为什么 BEGIN TRAN + COMMIT TRAN 不够用
默认情况下,SQL Server 遇到运行时错误(比如违反 NOT NULL、类型转换失败)只回滚出错那一条语句,其余语句照常执行。你写了个转账逻辑,前半句扣款失败,后半句加款却成功了,数据就乱了。
-
SET XACT_ABORT OFF是默认行为,也是隐患根源 -
@@ERROR只反映上一条语句的错误状态,容易漏判 - 没做
IF @@TRANCOUNT > 0检查就直接COMMIT,可能报The COMMIT TRANSACTION request has no corresponding BEGIN TRANSACTION
SET XACT_ABORT ON 是最简可靠的兜底方案
它让整个批处理在任何运行时错误发生时立刻终止,并自动回滚整个事务。适合逻辑线性、无分支判断的场景。
- 必须放在
BEGIN TRAN之前,否则无效 - 对编译错误(如语法错、对象不存在)不起作用
- 和
TRY...CATCH不冲突,但二者选其一即可;XACT_ABORT ON更轻量
CREATE PROCEDURE usp_Transfer
@fromId INT, @toId INT, @amount MONEY
AS
BEGIN
SET XACT_ABORT ON; -- 关键:必须放最前
BEGIN TRAN;
UPDATE Accounts SET Balance = Balance - @amount WHERE Id = @fromId;
UPDATE Accounts SET Balance = Balance + @amount WHERE Id = @toId;
COMMIT TRAN;
END用 TRY...CATCH 处理需自定义错误响应的场景
当你需要记录错误日志、返回特定错误码、或在回滚前做清理操作(比如发通知、写审计表),TRY...CATCH 是唯一选择。
-
CATCH块里必须先检查@@TRANCOUNT,再决定是ROLLBACK还是忽略 -
ERROR_MESSAGE()和ERROR_NUMBER()只在CATCH内有效 - 不要在
TRY块里提前COMMIT,否则CATCH无法回滚已提交的部分
CREATE PROCEDURE usp_WithLogOnFailure
AS
BEGIN
BEGIN TRY
BEGIN TRAN;
INSERT INTO Orders (OrderId, Status) VALUES (123, 'Pending');
INSERT INTO Orders (OrderId, Status) VALUES (123, 'Shipped'); -- 主键冲突
COMMIT TRAN;
END TRY
BEGIN CATCH
IF @@TRANCOUNT > 0 ROLLBACK TRAN;
INSERT INTO ErrorLog (Msg, ErrNo)
VALUES (ERROR_MESSAGE(), ERROR_NUMBER());
THROW; -- 或 RETURN -1
END CATCH
END
@@ERROR 检查只适用于老版本兼容或极简逻辑
它依赖逐条判断,代码冗长易漏,且无法捕获某些严重错误(如死锁、超时)。仅建议用于 SQL Server 2000 兼容场景,或单语句事务。
- 每条 DML 后必须紧跟
IF @@ERROR 0判断 - 不能跨批次保留值,
@@ERROR在下一条语句执行后即被覆盖 - 推荐改用
TRY...CATCH,除非明确受限于旧环境
真正容易被忽略的是:事务是否开启、是否已提交、是否被外部调用者嵌套——这些状态全靠 @@TRANCOUNT 维护。写存储过程时,别假设自己是事务的唯一控制者。

















