So, you have stumbled across this SET option? Lemme guess. You've had some problems with locking or poor performance. Perhaps you just discovered some conditional logic in your stored procedures whereby BEGIN TRANSACTION,
some error and COMMIT TRANSACTION don't flow quite the way you thought they would. What are your options? If you think XACT_ABORT is your salvation, read on.
At first glance, XACT_ABORT ON (it defaults to OFF) seems like the right answer:
- It seems plausible. As soon as an error is encountered, roll back that transaction! Release those locks! Abort! Who better to implement this than the DBMS itself?
- It's surgical. One call to sp_configure and all users will pick it up when they next create a connection. But wait, you only have a few users and they are all currently caching hundreds of connections on who knows how many web servers. Time for Plan B: alter all the sproc definitions to SET XACT_ABORT ON right at the top of the procedure definition. It's easy to script, and takes effect instantly.
Here is where I am going to go ahead and let you know that the XACT_ABORT setting is a little, well, misunderstood.
It doesn't always abort the transaction.
Observe:
Set XACT_ABORT On
Begin Transaction
RaisError ( N'Uh Oh!', 16, 1 )
If @@Error <> 0
begin print 'My rollback' rollback transaction end
This yields the following:
Msg 50000, Level 16, State 1, Line 3
Uh Oh!
My rollback
As you can see, the transaction wasn't aborted at all. SQL Server seems to deem this scenario unworthy of the XACT_ABORT ON treatment. This is somewhat forgivable. After all, we hadn't changed any state or locked any resources. But it's only somewhat forgivable. If we didn't know the behavior and this was a business logic test that we were safely handling, we might not think it necessary to properly code the commit or rollback. But that's not the whole story. Let's actually do something transactional and see what we get.
Observe:
Set XACT_ABORT On
Begin Transaction
update dbo.TestXACT1 set mySmallInt = 0 where myInt = 1
print @@TranCount
RaisError ( N'Uh Oh!', 16, 1 )
If @@Error <> 0
begin print @@TranCount print 'My rollback' rollback transaction end
This yields the following:
(1 row(s) affected)
1
Msg 50000, Level 16, State 1, Line 5
Uh Oh!
1
My rollback
Oh dear. What does this mean? We were transacting. There was an @@Error. But no transaction rollback occurred.
It does so much more than just rollback the transaction.
Lets go ahead and invoke a case where we can rest assured SQL Server will actually roll something back.
Observe:
Set XACT_ABORT On
Begin Transaction
update dbo.TestXACT1 set mySmallInt = 32768 -- clearly not small
where myInt = 1
print @@TranCount
If @@Error <> 0
begin
Print 'Couldn''t update that myInt 1'
RaisError ( N'Oh this is bad! Alert the troops!', 16, 1 ) with log
rollback transaction
end
This yields the following:
Msg 220, Level 16, State 1, Line 3
Arithmetic overflow error for data type smallint, value = 32768.
I can assure you that the transaction is indeed gone. Terminated. Aborted. But then so is your code. It isn't just the transaction that this setting aborts. It's the entire batch. Wait, batch? As in???
Observe:
Create procedure sp_TestXACT1 as
update dbo.TestXACT1 set mySmallInt = 32768 -- clearly not small
where myInt = 1
return -1
go
Set XACT_ABORT On
Begin Transaction
Declare @RetCode int
update dbo.TestXACT1 set mySmallInt = 0
where myInt = 1
print @@TranCount
Exec @RetCode = sp_TestXACT1
print @RetCode
If @RetCode <> 0
begin
RaisError ( N'Badness happened when I called sp_TestXACT1!!', 16, 1 ) with log
rollback transaction
end
This yields the following:
(1 row(s) affected)
1
Msg 220, Level 16, State 1, Procedure sp_TestXACT1, Line 3
Arithmetic overflow error for data type smallint, value = 32768.
So yeah, the whole batch. From somewhere deep within the stack of sprocs which called other sprocs which called even more sprocs came the one line of code which ended consideration of all other lines of code. The XACT was ABORTed and a whole lot more.
So where does this leave us? In my mind XACT_ABORT has limited applications. Certainly spend some time looking over your code before you decide what is best for your situation. Robust error handling has always been hard, no matter what language you code. These are your transactions and you are far better equipped to deal with them than your vendor.
I tested the code above on SQL 2005. There is new TRY,CATCH functionality available which could change the arguments, but I haven't had time to review.