Using TransactionScope around a stored procedure with transaction in SQL Server 2014

[亡魂溺海] 提交于 2019-12-02 19:33:53
StuartLC

Yes, TransactionScope can still work when wrapping a TSQL BEGIN / COMMIT TRANSACTION or an ADO SqlConnection.BeginTransaction. When wrapping a single connection, the behaviour is similar to nesting transactions in Sql:

  • @@TranCount will be incremented on each BEGIN TRAN

  • COMMIT TRAN will simply decrement @@TRANCOUNT. The transaction will only be committed if @@TRANCOUNT hits zero.

However:

  • ROLLBACK TRAN will abort the whole transaction (i.e. @@TRANCOUNT to zero), unless you are using Save Points (i.e. SAVE TRANSACTION xx ... ROLLBACK TRANSACTION xx.
  • When using stored procedures, you will receive an error if the connection's @@TRANCOUNT differs when exiting the SPROC from the value it had when entering a SPROC.

As a result, it is typically much easier to leave transaction semantics to TransactionScope and remove any manual BEGIN TRAN / COMMIT TRAN logic from cluttering up your TSQL.

Edit - clarification of the comments below

  • In the OP's case, the SPROC has NOT been written with nested transactions in mind (i.e. whether wrapped by an Sql or .Net outer transaction), specifically, the ROLLBACK in the BEGIN CATCH block will abort the entire outer transaction and will likely cause further errors in the outer TransactionScope as the @@TRANCOUNT rule has not been adhered to. A nested transaction pattern such as this should be observed if a SPROC needs to operate in both a nested or standalone transaction fashion.

  • SavePoints do not work with Distributed transactions, and TransactionScope can easily escalate into a distributed transaction e.g. if you are using different connection strings or controlling other resources in under the transaction scope.

As a result, I would recommend refactoring the PROC into a just the 'happy' core / inner case, calling this inner proc from the Transaction Scope, and doing any exception handling and rollback there. If you also need to call the proc from Ad Hoc Sql, then provide an external wrapper Proc which has the exception handling:

-- Just the happy case. This is called from .Net TransactionScope
CREATE PROC dbo.InnerNonTransactional
  AS
    BEGIN 
      UPDATE TABLE1...
      UPDATE TABLE2 ....
    END;

-- Only needed if you also need to call this elsewhere, e.g. from AdHoc Sql
CREATE PROC dbo.OuterTransactional
  AS
    BEGIN
      BEGIN TRY
        BEGIN TRAN
            EXEC dbo.InnerNonTransactional
        COMMIT TRAN
      END TRY
      BEGIN CATCH
         -- Rollback and handling code here.
      END CATCH
    END;
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!