Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Nested stored procedures containing TRY CATCH ROLLBACK pattern?

I'm interested in the side effects and potential problems of the following pattern:

CREATE PROCEDURE [Name] AS BEGIN     BEGIN TRANSACTION     BEGIN TRY         [...Perform work, call nested procedures...]     END TRY     BEGIN CATCH         ROLLBACK TRANSACTION         RAISERROR [rethrow caught error using @ErrorNumber, @ErrorMessage, etc]     END CATCH END 

To the best of my understanding this pattern is sound when used with a single procedure - the procedure will either complete all of its statements without error, or it will rollback all actions and report the error.

However when one stored procedure calls another stored procedure to do some sub-unit of work (with the understanding that the smaller procedure is sometimes called on its own) I see an issue coming about with relation to rollbacks - an informational message (Level 16) is issued stating The ROLLBACK TRANSACTION request has no corresponding BEGIN TRANSACTION.. This I assume is because the rollback in the sub-procedure is always rolling back the outer-most transaction, not just the transaction started in the sub-procedure.

I do want the whole thing rolled back and aborted if any error occurs (and the error reported to the client as an SQL error), I'm just not sure of all the side effects that come from the outer layers trying to rollback a transaction that has already been rolled back. Perhaps a check of @@TRANCOUNT before doing a rollback at each TRY CATCH layer?

Finally there is the client end (Linq2SQL), which has it's own transaction layer:

try {     var context = new MyDataContext();     using (var transaction = new TransactionScope())     {                    // Some Linq stuff         context.SubmitChanges();         context.MyStoredProcedure();         transactionComplete();     } } catch {     // An error occured! } 

In the event that a stored procedure, "MySubProcedure", called inside MyStoredProcedure raises an error, can I be sure that everything previously done in MyStoredProcedure will be rolled back, all the Linq operations made by SubmitChanges will be rolled back, and finally that the error will be logged? Or what do I need to change in my pattern to ensure the whole operation is atomic, while still allowing the child parts to be used individually (i.e. the sub-procedures should still have the same atomic protection)

like image 785
David Avatar asked Jan 15 '10 18:01

David


People also ask

Can nested transaction rollback in stored procedure?

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.

Can we use nested try catch in SQL Server?

TRY... CATCH constructs can be nested.

Can we use try and catch in stored procedure and function?

CATCH Blocks in SQL Server. Note that you cannot use TRY... CATCH blocks inside T-SQL UDFs. If you have to capture errors that occur inside a UDF, you can do that in the calling procedure or code.

What is nested stored procedure in SQL Server?

Nesting stored procedures means you have stored procedures that call stored procedures; each stored procedure may or may not have a transaction. To trap non-fatal errors in a called stored procedure, the called procedure must have some way to communicate back to the calling procedure that an error has occurred.


1 Answers

This is our template (error logging removed)

This is designed to handle

  • Paul Randal's article "No such thing as a nested transaction in SQL Server"
  • Error 266
  • Trigger Rollbacks

Explanations:

  • all TXN begin and commit/rollbacks must be paired so that @@TRANCOUNT is the same on entry and exit

  • mismatches of @@TRANCOUNT cause error 266 because

    • BEGIN TRAN increments @@TRANCOUNT

    • COMMIT decrements @@TRANCOUNT

    • ROLLBACK returns @@TRANCOUNT to zero

  • You can not decrement @@TRANCOUNT for the current scope
    This is what you'd think is the "inner transaction"

  • SET XACT_ABORT ON suppresses error 266 caused by mismatched @@TRANCOUNT
    And also deals with issues like this "SQL Server Transaction Timeout" on dba.se

  • This allows for client side TXNs (like LINQ) A single stored procedure may be part of distributed or XA transaction, or simply one initiated in client code (say .net TransactionScope)

Usage:

  • Each stored proc must conform to the same template

Summary

  • So don't create more TXNs than you need

The code

CREATE PROCEDURE [Name] AS SET XACT_ABORT, NOCOUNT ON  DECLARE @starttrancount int  BEGIN TRY     SELECT @starttrancount = @@TRANCOUNT      IF @starttrancount = 0         BEGIN TRANSACTION         [...Perform work, call nested procedures...]      IF @starttrancount = 0          COMMIT TRANSACTION END TRY BEGIN CATCH     IF XACT_STATE() <> 0 AND @starttrancount = 0          ROLLBACK TRANSACTION;     THROW;     --before SQL Server 2012 use      --RAISERROR [rethrow caught error using @ErrorNumber, @ErrorMessage, etc] END CATCH GO 

Notes:

  • The rollback check is actually redundant because of SET XACT_ABORT ON. However, it makes me feel better, looks odd without, and allows for situations where you don't want it on

  • Remus Rusanu has a similar shell that uses save points. I prefer an atomic DB call and don't use partial updates like their article

like image 188
gbn Avatar answered Sep 17 '22 14:09

gbn