从被调用的存储过程回滚事务

时间:2016-06-11 08:31:36

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

我有一个简单的场景:记录器程序和调用记录器的主程序。我试图在main中启动的logger中回滚事务,但是收到错误。我不知道为什么。以下是两个过程和我收到的错误消息:

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之后计数表示BEGIN和COMMIT的数量不匹配   声明。先前的计数= 1,当前计数= 0。

2 个答案:

答案 0 :(得分:3)

1)错误信息是明确的:SP末尾的活动TX数量应与开头的活动TX数量相同。

因此,当执行dbo.spLogger时,如果我们在此SP内执行@@TRANCOUNT语句,那么有效TX(1)的数量将为ROLLBACK。将取消所有有效广告,@@TRANCOUNT变为0 - > error/exception

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

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)只有在dbo.spLogger时,我才会在CATCH块内致电@@TRANCOUNT = 0

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+

  

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

答案 1 :(得分:0)

保留所有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找到答案:

如果你的调用者启动了一个事务并且calee命中了一个死锁(它中止了事务),那么被调用者如何与调用者通信该事务被中止并且它不应该像往常一样继续“?唯一可行的方法是重新引发异常,强制调用者处理这种情况。 如果你静默地吞下一个中止的事务并且调用者继续假设仍在原始事务中,那么只有混乱才能确保(并且你得到的错误就是引擎试图保护自己的方式)。

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