从一个存储过程启动3个存储过程时如何回滚

use*_*342 27 sql-server stored-procedures transaction

我有一个存储过程,它只在其中执行 3 个存储过程。如果主 SP 成功,我只使用 1 个参数来存储。

如果第一个存储过程在主存储过程中工作正常,但第二个存储过程失败,那么它会自动回滚主 SP 中的所有 SP 还是我必须发出一些命令?

这是我的程序:

CREATE PROCEDURE [dbo].[spSavesomename] 
    -- Add the parameters for the stored procedure here

    @successful bit = null output
AS
BEGIN
begin transaction createSavebillinginvoice
    begin Try
    -- SET NOCOUNT ON added to prevent extra result sets from
    -- interfering with SELECT statements.
    SET NOCOUNT ON;

   BEGIN 

   EXEC [dbo].[spNewBilling1]

   END

   BEGIN 

   EXEC [dbo].[spNewBilling2]

   END

   BEGIN 

   EXEC [dbo].[spNewBilling3]

   END 

   set @successful  = 1

   end Try

    begin Catch
        rollback transaction createSavesomename
        insert into dbo.tblErrorMessage(spName, errorMessage, systemDate) 
             values ('spSavesomename', ERROR_MESSAGE(), getdate())

        return
    end Catch
commit transaction createSavesomename
return
END

GO
Run Code Online (Sandbox Code Playgroud)

Sol*_*zky 62

鉴于只有在问题中显示的代码,并假设没有这三个子特效有任何明确的事务处理,那么,在任何三个子特效的错误将被捕获,并且ROLLBACKCATCH块将回滚所有的工作。

但是这里有一些关于事务的注意事项(至少在 SQL Server 中):

  • 无论您调用多少次,都只有一次真正的交易(第一次)BEGIN TRAN

    • 您可以命名一个事务(如你在这里做)这个名字将出现在日志中,但命名仅对于第一/最外层的交易(因为再次,第一个是交易)。
    • 每次调用时BEGIN TRAN,无论是否命名,事务计数器都会增加 1。
    • 您可以通过执行查看当前级别 SELECT @@TRANCOUNT;
    • 在 2 或以上COMMIT时发出的任何命令只会@@TRANCOUNT减少事务计数器,一次一个。
    • 没有什么是承诺,直到COMMIT发出时,@@TRANCOUNT1
    • 以防万一上面的信息没有明确表明:无论事务级别如何,都没有实际的事务嵌套。
  • 保存点允许事务中创建可以撤消的工作子集。

    • 通过SAVE TRAN {save_point_name}命令创建/标记保存点
    • 保存点标志着可以在不回滚整个事务的情况下撤消的工作子集的开始
    • 保存点名称不需要唯一,但多次使用相同名称仍会创建不同的保存点。
    • 保存点可以嵌套。
    • 无法提交保存点。
    • 保存点可以通过 撤消ROLLBACK {save_point_name}。(更多关于这个下面)
    • 回滚保存点将撤消在最近一次调用之后发生的任何工作SAVE TRAN {save_point_name},包括在创建回滚的保存点之后创建的任何保存点(因此是“嵌套”)。
    • 回滚保存点对事务计数/级别没有影响
    • SAVE TRAN除非发出完整ROLLBACK的整个交易,否则在初始之前完成的任何工作都无法撤消。
    • 需要明确的是:在 2 或更高COMMIT时发出when@@TRANCOUNT对保存点没有影响(因为同样,在该计数器之外不存在高于 1 的事务级别)。
  • 您不能提交特定的命名事务。事务“名称”(如果与 一起提供)将COMMIT被忽略并且仅存在于可读性上。

  • ROLLBACK没有名称的发出将始终回滚所有事务。

  • ROLLBACK带有名称的发行必须对应于:

    • 第一个事务,假设它被命名为:
      假设没有SAVE TRAN使用相同的事务名称调用,这将回滚所有事务。
    • “保存点”(如上所述):
      此行为将“撤消”自最近 SAVE TRAN {save_point_name}调用以来所做的所有更改。
    • 如果第一个事务 a) 已命名并且 b) 已SAVE TRAN发出以其名称发出的命令,则该事务名称的每个 ROLLBACK 将撤消每个保存点,直到该名称不再存在。之后,以该名称发出的 ROLLBACK 将回滚所有事务。
    • 例如,假设以下命令按所示顺序运行:

      BEGIN TRAN A -- @@TRANCOUNT is now 1
      -- DML Query 1
      SAVE TRAN A
      -- DML Query 2
      SAVE TRAN A
      -- DML Query 3
      
      BEGIN TRAN B -- @@TRANCOUNT is now 2
      SAVE TRAN B
      -- DML Query 4
      
      Run Code Online (Sandbox Code Playgroud)

      现在,如果您发出(以下每种情况相互独立):

      • ROLLBACK TRAN B一次:它将撤消“DML 查询 4”。@@TRANCOUNT还是2。
      • ROLLBACK TRAN B两次:它将撤消“DML 查询 4”,然后错误,因为“B”没有相应的保存点。@@TRANCOUNT还是2。
      • ROLLBACK TRAN A一次:它将撤消“DML 查询 4”和“DML 查询 3”。@@TRANCOUNT还是2。
      • ROLLBACK TRAN A两次:它将撤消“DML 查询 4”、“DML 查询 3”和“DML 查询 2”。@@TRANCOUNT还是2。
      • ROLLBACK TRAN A三次:它将撤消“DML 查询 4”、“DML 查询 3”和“DML 查询 2”。然后它将回滚整个事务(剩下的就是“DML 查询 1”)。@@TRANCOUNT现在是 0。
      • COMMIT一次:@@TRANCOUNT下降到 1。
      • COMMIT一次ROLLBACK TRAN B又一次:@@TRANCOUNT下降到 1。然后它将撤消“DML 查询 4”(证明 COMMIT 没有做任何事情)。@@TRANCOUNT还是1。
  • 事务名称和保存点名称:

  • 存储过程本身并不是隐式事务。如果未启动显式事务,则每个查询都是隐式事务。这就是为什么不需要围绕单个查询进行显式事务的原因,除非可以出于编程原因执行 a ROLLBACK,否则查询中的任何错误都是该查询的自动回滚。

  • 调用存储过程时,它必须以与@@TRANCOUNT调用时相同的值退出。意思是,你不能:

    • BEGIN TRAN在 proc 中启动 a而不提交它,期望在调用/父进程中提交。
    • ROLLBACK如果在调用 proc 之前启动了显式事务,则不能发出 a ,因为它将返回@@TRANCOUNT0。

    如果退出存储过程时事务计数高于或低于启动时,您将收到类似于以下内容的错误:

    消息 266,级别 16,状态 2,过程 YourProcName,行 0
    EXECUTE 后的事务计数表示 BEGIN 和 COMMIT 语句的数量不匹配。先前计数 = X,当前计数 = Y。

  • 表变量,就像常规变量一样,不受事务的约束。


关于在 procs 中进行事务处理,可以独立调用(因此需要事务处理)或从其他 procs 调用(因此不需要事务处理):这可以通过几种不同的方式完成。

几年来我一直在处理它似乎运作良好的方式是只BEGIN/ COMMIT/ROLLBACK在最外层。子过程调用只是跳过事务命令。我在下面概述了我放入每个 proc 的内容(嗯,每个都需要事务处理)。

  • 在每个过程的顶部, DECLARE @InNestedTransaction BIT;
  • 代替 simple BEGIN TRAN,请执行以下操作:

    IF (@@TRANCOUNT = 0)
    BEGIN
       SET @InNestedTransaction = 0;
       BEGIN TRAN; -- only start a transaction if not already in one
    END;
    ELSE
    BEGIN
       SET @InNestedTransaction = 1;
    END;
    
    Run Code Online (Sandbox Code Playgroud)
  • 代替 simple COMMIT,请执行以下操作:

    IF (@@TRANCOUNT > 0 AND @InNestedTransaction = 0)
    BEGIN
       COMMIT;
    END;
    
    Run Code Online (Sandbox Code Playgroud)
  • 代替 simple ROLLBACK,请执行以下操作:

    IF (@@TRANCOUNT > 0 AND @InNestedTransaction = 0)
    BEGIN
       ROLLBACK;
    END;
    
    Run Code Online (Sandbox Code Playgroud)

无论事务是在 SQL Server 内启动还是在应用程序层启动,此方法都应该以相同的方式工作。

有关TRY...CATCH构造中此事务处理的完整模板,请参阅我对以下 DBA.SE 问题的回答:我们是否需要在 C# 代码以及存储过程中处理事务


除了“基础知识”之外,还有一些交易的细微差别需要注意:

  • 默认情况下,事务在大多数情况下不会在发生错误时自动回滚/取消。只要您有适当的错误处理并给ROLLBACK自己打电话,这通常不是问题。但是,有时事情会变得复杂,例如在批处理中止错误的情况下,或者在使用OPENQUERY(或通常的链接服务器)时,远程系统上发生错误。虽然大多数错误都可以使用 捕获TRY...CATCH,但有两个错误无法通过这种方式捕获(虽然现在不记得是哪个——正在研究中)。在这些情况下,您必须使用SET XACT_ABORT ON来正确回滚事务。

    SET XACT_ABORT ON使 SQL Server立即回滚任何事务(如果一个事务处于活动状态)在发生任何错误时中止批处理。此设置在引入TRY...CATCH构造的SQL Server 2005 之前就已存在。在TRY...CATCH大多数情况下,可以处理大多数情况,因此大多数情况下都不再需要XACT_ABORT ON. 但是,在使用时OPENQUERY(可能还有我目前不记得的另一种情况),那么您仍然需要使用SET XACT_ABORT ON;.

  • 在触发器内部,XACT_ABORT隐式设置为ON。这会导致触发器中的任何错误取消触发触发器的整个 DML 语句。

  • 您应该始终进行适当的错误处理,尤其是在使用事务时。TRY...CATCHSQL Server 2005 中引入的构造提供了一种处理几乎所有情况的方法,这是对@@ERROR每个语句之后的测试的可喜改进,这对批处理中止错误没有太大帮助。

    TRY...CATCH然而,引入了一个新的“状态”。当使用TRY...CATCH结构,如果你有一个活跃的交易并发生错误,那么还有一些可以采取几种途径:

    • XACT_ABORT OFF和语句中止错误:事务仍处于活动状态,处理继续执行下一个语句(如果有)。
    • XACT_ABORT OFF和大多数批处理中止错误:事务仍处于活动状态,处理继续进行下一个批处理(如果有)。
    • XACT_ABORT OFF和某些批处理中止错误:事务被回滚并且处理继续下一个批处理,如果有的话。
    • XACT_ABORT ON任何错误:事务被回滚,处理继续下一批,如果有的话。


    然而,当使用 时TRY...CATCH,批处理中止错误不会中止批处理,而是将控制转移到CATCH块。如果XACT_ABORTOFF,该交易仍活跃,绝大多数的时间,你会需要COMMIT,或最有可能的,ROLLBACK。但是当遇到某些批处理中止错误(例如 with OPENQUERY),或者当XACT_ABORTis 时ON,事务将处于新状态,“不可提交”。在这种状态下,您不能COMMIT,也不能进行任何 DML 操作。你所能做的就是ROLLBACKSELECT陈述。然而,在这种“不可提交”的状态下,事务在发生错误时被回滚,发布ROLLBACK只是一种形式,但必须完成。

    函数XACT_STATE可用于确定事务是活动的、不可提交的还是不存在的。建议(至少某些人)在CATCH块中检查此函数以确定结果是否-1(即不可提交)而不是测试 if @@TRANCOUNT > 0。但是对于XACT_ABORT ON,那应该是唯一可能的状态,因此似乎测试@@TRANCOUNT > 0XACT_STATE() <> 0是等效的。另一方面,当XACT_ABORTOFF并且有一个活动交易时,那么可能有一个状态1-1CATCH块中,这允许发行COMMIT而不是ROLLBACK(虽然,我想不出当有人会想要COMMIT如果事务是可提交的)。有关XACT_STATE()CATCH块内使用的更多信息和研究XACT_ABORT ON可以在我对以下 DBA.SE 问题的回答中找到:当 XACT_ABORT 设置为 ON 时,在什么情况下可以从 CATCH 块内部提交事务?. 请注意,有一个小错误XACT_STATE()会导致它1在某些情况下错误地返回:XACT_STATE() 在 SELECT 中使用某些系统变量但没有 FROM 子句时返回 1


关于原始代码的说明:

  • 您可以删除为交易指定的名称,因为它没有任何帮助。
  • 你并不需要BEGINEND周围的每个EXEC通话

  • 这是一个非常好的,很好的答案。 (2认同)

Aas*_*lah 2

是的,如果由于主存储过程的 catch 语句中的任何错误而执行回滚代码,它将回滚由任何直接语句或通过其中的任何嵌套存储过程执行的所有操作。

即使您没有在嵌套存储过程中应用任何显式事务,这些存储过程仍然会使用隐式事务,并将在完成时提交,但是您在嵌套存储过程中通过显式或隐式事务提交,SQL Server 引擎将忽略它,并将如果主存储过程失败并且事务回滚,则回滚这些嵌套存储过程的所有操作。

每次根据最外层事务结束时采取的操作来提交或回滚事务。如果提交了外部事务,则内部嵌套事务也会提交。如果回滚外部事务,则所有内部事务也会回滚,无论内部事务是否单独提交。

供参考http://technet.microsoft.com/en-us/library/ms189336(v=sql.105).aspx