Rollback the inner transaction of nested transaction

后端 未结 5 1848
天命终不由人
天命终不由人 2020-12-05 02:43

suppose I have following sql statement in sql server 2008:

BEGIN TRANSACTION    
SqlStatement1    
EXEC sp1    
SqlStatement3
COMMIT TRANSACTION
相关标签:
5条回答
  • 2020-12-05 03:26

    Rollback transaction on its own rolls back all transactions.

    http://msdn.microsoft.com/en-us/library/ms181299(v=sql.100).aspx

    The statement will still be executed - try this

    create table #t (i int)
    insert #t values (1)  -- t contains (1)
    
    begin tran
        update #t set i = i +1
        select * from #t  -- t contains (2)
        begin tran
            update #t set i = i +1 
            select * from #t -- t contains (3)
        rollback tran  -- transaction is rolled back
    
    select * from #t -- t contains (1)
    update #t set i = i +1
    select * from #t -- t contains (2)
    commit    -- error occurs
    select * from #t -- t contains (2)
    drop table #t
    
    0 讨论(0)
  • 2020-12-05 03:28

    SQL Server doesn't really support nested transactions. There is only one transaction at a time.

    This one transaction has a basic nested transaction counter, @@TRANCOUNT. Each consecutive begin transaction increments the counter by one, each commit transaction reduces it by one. Only the commit that reduces the counter to 0 really commits the one transaction.

    A rollback transaction undoes the one transaction and clears @@TRANCOUNT.

    In your case, the funny result is that SqlStatement3 is run outside a transaction! Your final commit will throw an "The COMMIT TRANSACTION request has no corresponding BEGIN TRANSACTION" exception, but the effects of SqlStatement3 are permanent.

    For example:

    create table #t (col1 int)
    insert #t (col1) values (1)
    BEGIN TRANSACTION
    update #t set col1 = 2 -- This gets rolled back
    BEGIN TRANSACTION
    update #t set col1 = 3 -- This gets rolled back too
    ROLLBACK TRANSACTION
    update #t set col1 = 4 -- This is run OUTSIDE a transaction!
    COMMIT TRANSACTION -- Throws error
    select col1 from #t
    

    Prints 4. Really. :)

    0 讨论(0)
  • 2020-12-05 03:28

    Nested transactions can be used. To only rollback the inner transaction, use a savepoint and rollback to the savepoint. In case the inner transaction does not whether it is nested or not, IF statements can be used to find out whether to set a savepoint and whether to rollback or to rollback to a savepoint:

    BEGIN TRAN
    
        DECLARE @WILL_BE_NESTED_TRANSACTION BIT = CASE WHEN (@@TRANCOUNT > 0) THEN 1 ELSE 0 END
        IF @WILL_BE_NESTED_TRANSACTION = 1
            SAVE TRAN tran_save
        BEGIN TRAN
            -- do stuff
    
        IF @WILL_BE_NESTED_TRANSACTION = 1
            ROLLBACK TRAN tran_save
        ELSE
            ROLLBACK
    
    ROLLBACK
    
    0 讨论(0)
  • 2020-12-05 03:38

    Committing inner transactions is ignored by the SQL Server Database Engine. The transaction is either committed or rolled back based on the action taken at the end of the outermost transaction. If the outer transaction is committed, the inner nested transactions are also committed. If the outer transaction is rolled back, then all inner transactions are also rolled back, regardless of whether or not the inner transactions were individually committed.

    Nesting Transactions in Microsoft TechNet

    0 讨论(0)
  • 2020-12-05 03:44

    You can use transaction savepoints. sp1 can use a pattern like the one described in Error Handling and Nested Transactions:

    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;
    
            raiserror ('usp_my_procedure_name: %d: %s', 16, 1, @error, @message) ;
        end catch   
    end
    

    Such a pattern allow for the work done in sp1 to rollback, but keep the encompassing transaction active.

    0 讨论(0)
提交回复
热议问题