【问题标题】:Rollback transaction from called stored procedure从调用的存储过程回滚事务
【发布时间】:2016-06-11 08:31:35
【问题描述】:

我有一个简单的场景:记录器过程和调用记录器的主过程。我正在尝试在主要启动的记录器中回滚事务,但出现错误。我不确定为什么。这是我收到的两个过程和错误消息:

CREATE PROCEDURE spLogger
AS
BEGIN
    IF @@TRANCOUNT > 0
    BEGIN
       PRINT @@TRANCOUNT
       ROLLBACK
    END
END
GO

CREATE PROCEDURE spCaller
AS
BEGIN
    BEGIN TRY
       BEGIN TRANSACTION
          RAISERROR('', 16, 1)
       COMMIT TRANSACTION
    END TRY
    BEGIN CATCH
       EXEC spLogger
    END CATCH
END
GO

EXEC spCaller

1 消息 266,级别 16,状态 2,过程 spLogger,第 15 行事务 EXECUTE 后的 count 表示 BEGIN 和 COMMIT 的数量不匹配 陈述。先前计数 = 1,当前计数 = 0。

【问题讨论】:

    标签: sql-server transactions sql-server-2014


    【解决方案1】:

    1) 错误信息很明确:SP 结束时的活跃 TX 数应与开始时的活跃 TX 数相同。

    所以,当 dbo.spLogger 开始执行时,活动 TX (@@TRANCOUNT) 的数量为 1,如果我们在此 SP 中执行 ROLLBACK 语句,这将取消 ALL活跃的 TX 和 @@TRANCOUNT 变为 0 -> error/exception

    2) 如果您只想避免在每个用户 SP 的每个 CATCH 块中写入 IF @@TRANCOUNT ... ROLLBACK,那么不要这样做。在ROLLBACK 之后,我会在CATCH 块内调用dbo.spLogger

    3) 如果我必须使用 TX 从其他 SP 调用 SP,那么我将使用以下模板(来源:Rusanu's blog

    create procedure [usp_my_procedure_name]
    as
    begin
        set nocount on;
        declare @trancount int;
        set @trancount = @@trancount;
        begin try
            if @trancount = 0
                begin transaction
            else
                save transaction usp_my_procedure_name;
    
            -- Do the actual work here
    
    lbexit:
            if @trancount = 0   
                commit;
        end try
        begin catch
            declare @error int, @message varchar(4000), @xstate int;
            select @error = ERROR_NUMBER()
                                       , @message = ERROR_MESSAGE()
                                       , @xstate = XACT_STATE();
            if @xstate = -1
                rollback;
            if @xstate = 1 and @trancount = 0
                rollback
            if @xstate = 1 and @trancount > 0
                rollback transaction usp_my_procedure_name;
    
            throw;
        end catch   
    end
    

    有一些小的改动:

    a)SET XACT_ABORT ON

    b) 仅当有@@TRANCOUNT = 0 时,我才会在CATCH 块内调用dbo.spLogger

    IF @@TRANCOUNT = 0
    BEGIN
        EXEC dbo.spLogger ... params ...
    END
    THROW -- or RAISERROR(@message, 16, @xstate)
    

    为什么?因为如果dbo.spLogger SP 将在一个 TX 处于活动状态时将行插入到dbo.DbException 表中,那么在ROLLBACK 的情况下,SQL Server 也必须ROLLBACL 这些行。

    例子:

    SP1 -call-> SP2 -call-> SP3
                             |err/ex -> CATCH & RAISERROR (no full ROLLBACK)
                  <-----------
                  |err/ex -> CATCH & RAISERROR (no full ROLLBACK)
     <-------------
    |err/ex -> CATCH & FULL ROLLBACK & spLogger      
    

    4) 更新

    CREATE PROC TestTx
    AS
    BEGIN
        BEGIN TRAN -- B
        ROLLBACK -- C
    END 
    -- D
    GO
    
    -- Test
    BEGIN TRAN -- A - @@TRANCOUNT = 1
    EXEC dbo.TestTx 
        /*
        Number of active TXs (@@TRANCOUNT) at the begining of SP is 1
        B - @@TRANCOUNT = 2
        C - @@TRANCOUNT = 0
        D - Execution of SP ends. SQL Server checks & generate an err/ex
            Transaction count after EXECUTE indicates a mismatching number of BEGIN and COMMIT statements. Previous count = 1, current count = 0.
        */
    COMMIT -- E - Because @@TRANCOUNT is 0 this statement generates 
    

    另一个错误/ex COMMIT TRANSACTION 请求没有对应的 BEGIN TRANSACTION。 -- 测试结束

    5) 见autonomous transactions: it requires SQL2008+

    自治事务本质上是一个嵌套事务,其中 内部事务不受外部状态的影响 交易。换句话说,你可以离开当前的上下文 事务(外部事务)并调用另一个事务 (自主交易)。一旦你完成了自主工作 交易,你可以回来继续在当前 交易。在自治事务中完成的事情是真正完成的 并且无论外部事务发生什么都不会改变。

    【讨论】:

    • 能否请您进一步澄清此声明“错误消息很清楚:SP 结束时的活动 TX 数量应与开始时的活动 TX 数量相同。”
    • 您好,当我在调用记录器之前进行回滚时,它确实有效。我怀疑这是我无法解决的限制...
    • @TheGameiswar,我想是--trancount here EXEC dbo.spLogger --and here should be the same
    • @Bogdan,我想我最终会创建一个 CLR 记录器过程,该过程将打开一个新连接并记录在任何情况下都会记录,因为它不会在事务中。
    【解决方案2】:

    抛开所有 xact_abort 的东西,我看不出为什么你应该得到错误。所以做了一些研究,这里是观察结果

    ----这行得通

    alter PROCEDURE spCaller
    AS
    BEGIN
        BEGIN TRY
           BEGIN TRANSACTION
              RAISERROR('', 16, 1)
           COMMIT TRANSACTION
        END TRY
        BEGIN CATCH
         rollback 
    
        END CATCH
    END
    GO
    

    ---同样如此,获取 sp 的文本并将其保存在 catch 块中

    alter PROCEDURE spCaller
    AS
    BEGIN
        BEGIN TRY
           BEGIN TRANSACTION
              RAISERROR('', 16, 1)
           COMMIT TRANSACTION
        END TRY
        BEGIN CATCH
         --rollback 
         IF @@TRANCOUNT > 0
        BEGIN
           PRINT @@TRANCOUNT
           ROLLBACK
        END
        END CATCH
    END
    GO
    

    经过一番研究,Remus Rusanu 在这里找到了答案:

    如果你的调用者启动了一个事务并且被调用者遇到了一个死锁(它中止了事务),那么被调用者将如何与调用者沟通事务被中止并且它不应该继续“一切照旧” '?唯一可行的方法是重新引发异常,强制调用者处理这种情况。 如果您默默吞下一个中止的事务,而调用者继续假设仍在原始事务中,则只有混乱可以确保(您得到的错误是引擎试图保护自己的方式)。

    在您的情况下,只有在使用存储过程并尝试引发错误时,您才会收到错误,因为存储过程会启动单独的数据上下文。您遇到的错误可能是 SQL 方式告诉这不起作用.

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2015-03-22
      • 1970-01-01
      • 2020-07-08
      • 1970-01-01
      • 1970-01-01
      • 2014-10-06
      • 2020-10-13
      相关资源
      最近更新 更多