HiveBrain v1.2.0
Get Started
← Back to all entries
patternsqlModerate

In what cases a transaction can be committed from inside the CATCH block when XACT_ABORT is set to ON?

Submitted by: @import:stackexchange-dba··
0
Viewed 0 times
canthecommittedwhatxact_abortblockcatchtransactionwheninside

Problem

I've been reading MSDN about TRY...CATCH and XACT_STATE.

It has the following example that uses XACT_STATE in the CATCH block of a TRY…CATCH construct to determine whether to commit or roll back a transaction:

USE AdventureWorks2012;
GO

-- SET XACT_ABORT ON will render the transaction uncommittable
-- when the constraint violation occurs.
SET XACT_ABORT ON;

BEGIN TRY
    BEGIN TRANSACTION;
        -- A FOREIGN KEY constraint exists on this table. This 
        -- statement will generate a constraint violation error.
        DELETE FROM Production.Product
            WHERE ProductID = 980;

    -- If the delete operation succeeds, commit the transaction. The CATCH
    -- block will not execute.
    COMMIT TRANSACTION;
END TRY
BEGIN CATCH
    -- Test XACT_STATE for 0, 1, or -1.
    -- If 1, the transaction is committable.
    -- If -1, the transaction is uncommittable and should 
    --     be rolled back.
    -- XACT_STATE = 0 means there is no transaction and
    --     a commit or rollback operation would generate an error.

    -- Test whether the transaction is uncommittable.
    IF (XACT_STATE()) = -1
    BEGIN
        PRINT 'The transaction is in an uncommittable state.' +
              ' Rolling back transaction.'
        ROLLBACK TRANSACTION;
    END;

    -- Test whether the transaction is active and valid.
    IF (XACT_STATE()) = 1
    BEGIN
        PRINT 'The transaction is committable.' + 
              ' Committing transaction.'
        COMMIT TRANSACTION;   
    END;
END CATCH;
GO


What I don't understand is, why should I care and check what XACT_STATE returns?

Please note, that the flag XACT_ABORT is set to ON in the example.

If there is a severe enough error inside the TRY block, the control will pass into CATCH. So, if I'm inside the CATCH, I know that transaction has had a problem and really the only sensible thing to do in this case is to roll it back, isn't it?

But, this example from MSDN implies that there ca

Solution

It turns out that transaction can not be committed from inside the CATCH block if XACT_ABORT is set to ON.

The example from MSDN is somewhat misleading, because the check implies that XACT_STATE can return 1 in some cases and it may be possible to COMMIT the transaction.

IF (XACT_STATE()) = 1
BEGIN
    PRINT 'The transaction is committable.' + 
          ' Committing transaction.'
    COMMIT TRANSACTION;   
END;


It is not true, XACT_STATE will never return 1 inside CATCH block if XACT_ABORT is set to ON.

It seems that the MSDN sample code was meant to primarily illustrate the use of XACT_STATE() function regardless of the XACT_ABORT setting.
The sample code looks generic enough to work with both XACT_ABORT set to ON and OFF.
It is just that with XACT_ABORT = ON the check IF (XACT_STATE()) = 1 becomes unnecessary.

There is a very good detailed set of articles about Error and Transaction Handling in SQL Server by Erland Sommarskog.
In Part 2 - Classification of Errors he presents a comprehensive table
that puts together all classes of errors and how they are handled by SQL Server and how TRY ... CATCH and XACT_ABORT changes the behaviour.

+-----------------------------+---------------------------++------------------------------+
| | Without TRY-CATCH || With TRY-CATCH |
+-----------------------------+-------+-------+-----+-----++------------------+-----+-----+
| SET XACT_ABORT | OFF | ON | OFF | ON || ON or OFF | OFF | ON |
+-----------------------------+-------+-------+-----+-----++------------------+-----+-----+
| Class Name | Aborts | Rolls || Catchable | Dooms |
| | | Back || |transaction|
+-----------------------------+-------+-------+-----+-----++------------------+-----+-----+
| Fatal errors | Connection | Yes || No | n/a |
| Batch-aborting | Batch | Yes || Yes | Yes |
| Batch-only aborting | Batch | No | Yes || Yes | No | Yes |
| Statement-terminating | Stmnt | Batch | No | Yes || Yes | No | Yes |
| Terminates nothing at all | Nothing | No || Yes | No | Yes |
| Compilation: syntax errors | (Statement) | No || Yes | No | Yes |
| Compilation: binding errors | Scope | Batch | No | Yes || Outer scope only | No | Yes |
| Compilation: optimisation | Batch | Yes || Outer scope only | Yes |
| Attention signal | Batch | No | Yes || No | n/a |
| Informational/warning msgs | Nothing | No || No | n/a |
| Uncatchable errors | Varying | Varying || No | n/a |
+-----------------------------+-------+-------+-----+-----++------------------+-----+-----+


The last column in the table answers the question. With TRY-CATCH and with XACT_ABORT ON the transaction is doomed in all possible cases.

One note outside the scope of the question. As Erland says, this consistency is one of the reasons to set XACT_ABORT to ON:


I have already given the recommendation that your stored procedures
should include the command SET XACT_ABORT, NOCOUNT ON. If you look
at the table above, you see that with XACT_ABORT in effect, there is
some higher level of consistency. For instance, the transaction is
always doomed. In the following, I will show many examples where I
set XACT_ABORT to OFF, so that you can get an understanding of why
you should avoid this default setting.

Code Snippets

IF (XACT_STATE()) = 1
BEGIN
    PRINT 'The transaction is committable.' + 
          ' Committing transaction.'
    COMMIT TRANSACTION;   
END;

Context

StackExchange Database Administrators Q#128535, answer score: 12

Revisions (0)

No revisions yet.