6

In a SQL Server code block, what is the best place to place the commit transaction? Inside the try catch block or outside it?.

For example, is option A or option B the correct approach or are they subjective choices?

Option A

CREATE PROCEDURE DummyProc 
BEGIN TRY
      BEGIN TRANSACTION
      INSERT sometable(a, b) VALUES (@a, @b)
      INSERT sometable(a, b) VALUES (@b, @a)
      COMMIT TRANSACTION
   END TRY
   BEGIN CATCH
      IF @@trancount > 0 ROLLBACK TRANSACTION
      DECLARE @msg nvarchar(2048) = error_message()  
      RAISERROR (@msg, 16, 1)
      RETURN 55555
   END CATCH   

Option B

CREATE PROCEDURE DummyProc 
BEGIN TRY
      BEGIN TRANSACTION
      INSERT sometable(a, b) VALUES (@a, @b)
      INSERT sometable(a, b) VALUES (@b, @a)

   END TRY
   BEGIN CATCH
      IF @@trancount > 0 ROLLBACK TRANSACTION
      DECLARE @msg nvarchar(2048) = error_message()  
      RAISERROR (@msg, 16, 1)
      RETURN 55555
   END CATCH
   IF @@trancount > 0  COMMIT TRANSACTION

In Option B, is there a possibility of some error happening when its doing a commit outside the TRY-CATCH block ?

1

2 Answers 2

9

The best way I have found to do this is the following code:

SET XACT_ABORT ON;

BEGIN TRY
    BEGIN TRANSACTION

    /*
        Code goes here
    */

    COMMIT TRANSACTION
END TRY
BEGIN CATCH

    DECLARE @ErrorMessage NVARCHAR(4000);
    DECLARE @ErrorSeverity INT;
    DECLARE @ErrorState INT;

    SELECT 
        @ErrorMessage = ERROR_MESSAGE(),
        @ErrorSeverity = ERROR_SEVERITY(),
        @ErrorState = ERROR_STATE();

    RAISERROR (@ErrorMessage, -- Message text.
               @ErrorSeverity, -- Severity.
               @ErrorState -- State.
               );

    -- If >= SQL 2012 replace all code in catch block above with
    -- THROW;

    WHILE @@TRANCOUNT > 0
    BEGIN
        ROLLBACK TRANSACTION;
    END

END CATCH

Note the use of XACT_ABORT to ensure the errors are caught effectively, the fact that both the BEGIN and COMMIT statements are within the TRY block, and the WHILE for @@Trancount - this should ensure nested transactions are rolled back (not always applicable)

The THROW statement can also replace RAISERROR for SQL versions above 2012, to rethrow the caught exception / error.

As Dan Guzman states in his comment, XACT_ABORT is useful for catching errors that the TRY/CATCH construct won't, including timeouts, runtime collation errors etc

1
  • 1
    If you use THROW instead of RAISEERROR, the ROLLBACK needs to happen before the THROW statement.: SELECT 'here'; RAISERROR('Error message 1', 16,1); SELECT 'here 2'; THROW 60000, 'Error message 2',1; SELECT 'here 3';
    – Woody
    Commented Nov 8, 2022 at 1:16
4

Option A is the correct choice. It is possible for all statements in a transaction to work and then the actual COMMIT to fail, so you keep the COMMIT inside your TRY block so that any failure of the COMMIT will be caught and you can gracefully handle this error and rollback.

See this answer on SE. In your example code, if we assume other users are running this procedure concurrently it might be possible for the TRY block to complete successfully and then fail on the COMMIT due to changes made by other sessions.

1
  • 5
    I agree and add that SET XACT_ABORT ON be added to the beginning of the proc so the transaction is rolled back immediately if a client timeout occurs (where the CATCH block won't be executed).
    – Dan Guzman
    Commented Mar 26, 2019 at 2:00

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.