在存储过程中使用 SAVE TRANSACTION SavePointName

Using SAVE TRANSACTION SavePointName in a Stored Procedure

我不清楚我是否需​​要为我使用的每个 SP 使用不同的保存点名称 SAVE TRANSACTION

我可以一直使用例如SAVE TRANSACTION ProcedureSavePointROLLBACK TRANSACTION ProcedureSavePoint 即使更高级别的事务使用相同的保存点名称?

我的SP签名如下:

ALTER PROCEDURE [dbo].[usp_MyTask]()
AS
BEGIN
    DECLARE @iReturn int = 0

    DECLARE @tranCount int = @@TRANCOUNT;
    IF @tranCount > 0
        SAVE TRANSACTION ProcSavePoint;
    ELSE
        BEGIN TRAN    
    ...

    IF <some condition>
    BEGIN
        @iReturn = 1
        GOTO Undo
    END

    ...

    IF @tranCount = 0 
        COMMIT TRAN 
    RETURN

Undo:
    IF @tranCount = 0 -- transaction started in procedure. Roll back complete transaction.
        ROLLBACK TRAN;
    ELSE
        IF XACT_STATE() <> -1 ROLLBACK TRANSACTION ProcSavePoint;

    RETURN @iReturn
END

希望我的问题很清楚。

从技术上讲,是的,您可以重复使用相同的保存点名称,它们将像多次调用 BEGIN TRAN 一样堆叠起来,每次调用 COMMIT 只会使计数器递减。意思是,如果你发出 SAVE TRANSACTION ProcSavePoint; 5 次,然后调用 ROLLBACK TRANSACTION ProcSavePoint; 2 次,你仍然会停留在第三次调用 SAVE TRAN 之后和调用它之前的状态第四次。

但是,此代码在几个层面上存在问题:

  1. 由于刚才提到的行为,在嵌套场景中,根据调用 GOTO Undo 的条件,如果您遇到调用嵌套过程 5 层深的情况,然后第 5 级成功完成,然后第 4 级成功完成,但随后第 3 级决定转到 "undo",它将执行 ROLLBACK TRANSACTION ProcSavePoint;,这只会回滚第 5 个级别。这会让您处于糟糕的状态,因为目的是回滚到级别 3 开始时的状态。

    使用唯一的保存点名称可以解决这个问题。

  2. 奇怪的是你没有使用 TRY / CATCH 结构。你真的应该。如果您有逻辑决定根据不是 SQL 服务器错误的特定条件取消操作,您仍然可以通过调用 RAISERROR() 立即转到 CATCH 来强制执行此操作堵塞。或者,如果您不想将其作为错误处理,除了 TRY / CATCH.

    [=69= 之外,您仍然可以执行 GOTO undo 方法]
  3. 我不相信 XACT_STATE() 可以在 TRY / CATCH 构造之外报告 -1

  4. 你为什么首先使用保存点?您是否遇到过外层可能会继续并最终 COMMIT 即使在子过程调用中发生错误的情况?

    我最常使用的模板显示在我对 DBA.StackExchange 这个问题的回答中:Are we required to handle Transaction in C# Code as well as in Store procedure。该模板只是在开始时检查活动事务(类似于您的方法),但如果存在活动事务则不执行任何操作。因此,永远不会调用额外的 BEGIN TRAN 甚至 SAVE TRAN,只有外部的稍后(即使它是应用程序代码)才会执行 COMMITROLLBACK

    只是为了指出这一点,因为它 看起来 就像你的代码和我在那个链接的答案中发布的代码之间的功能差异,但实际上不是:没有具体需要捕获 @@TRANCOUNT 的实际值,因为唯一的选项是 0> 0,并且除非 @@TRANCOUNT 在输入模板时已经 > 1,否则它将达到最大值ever get 无论如何都是 1(如果 Triggers and/or INSERT INTO ... EXEC 递增,即使存在活动事务,也可能是 2)。在任何一种情况下,我对 @InNestedTransaction 使用 BIT 变量在功能上/逻辑上等同于将 @@TRANCOUNT 存储在 INT 变量中,因为 SAVE TRAN 不会递增 @@TRANCOUNT.