I am seeing some strange behavior which has occurred on random basis.
Here's what my store procedure basically does.
begin try
begin tran
insert into table1
update table2
insert into table3
commit tran
end try
begin catch
rollback tran
end catch
For most of the time above code works fine except once in a while(once per day or two) when some error occurs, the transaction does not rollback the changes from all 3 tables.
begin try
begin tran
insert into table1----Rollback doesn't happen
update table2--Rollback happens
insert into table3--Rollback happens
commit tran
end try
begin catch
rollback tran
end catch
Can anyone please can suggest something where I might be wrong or do I need to handle transaction in some another way?
Thanks in advance.
Check your SET XACT_ABORT setting and what the error level inside the failing sql actually is
How to make SET XACT_ABORT ON rollback the transaction?
Related
We have recently been parachuted to a new ETL project with very bad code.
I have in my hands a query with 700 rows and all sort of update.
I would like to debug it with SET XACT_ABORT ON; and the goal is to rollback everything if only one transaction fails.
But I find several way to archive it on StackOverflow like this:
BEGIN TRANSACTION;
BEGIN TRY
-- Multiple sql statements goes here
COMMIT TRANSACTION;
END TRY
BEGIN CATCH
ROLLBACK TRANSACTION;
END CATCH
or this:
BEGIN TRY
BEGIN TRANSACTION
-- Multiple sql statements goes here
COMMIT TRANSACTION
END TRY
BEGIN CATCH
PRINT(ERROR_MESSAGE())
ROLLBACK TRANSACTION
END CATCH
and none of these uses SET XACT_ABORT ON;.
I don't understand, is SET XACT_ABORT ON the same as using BEGIN TRY BEGIN TRANSACTION?
Can I just use:
SET XACT_ABORT ON;
-- Multiple sql statements goes here
and get ridof all the:
BEGIN TRANSACTION;
BEGIN TRY
?
And also, should I use BEGIN TRANSACTION and then BEGIN TRY or the other way around?
It is not the same. It decides when errors are thrown.
You should always use SET XACT_ABORT ON, because it is more consistent; almost always, an error will stop execution and throw an error. Else, half things throw errors and the other half continue execution.
There is a great article about this whole subject on Erland Sommarskog's site, and if you go at this point you will see a table which describes this strange behaviour. In conclusion, I recommend the General Pattern for Error Handling, as it is very well documented as well as provide you the opportunity to tweak it according to its own documentation.
Thank you for the resources #George Menoutis.
I post here my practical solution:
SET XACT_ABORT ON;
BEGIN TRY
BEGIN TRANSACTION;
-- Multiple sql statements goes here
COMMIT TRANSACTION;
END TRY
BEGIN CATCH
IF ##TRANCOUNT > 0 ROLLBACK TRANSACTION;
THROW;
END CATCH;
GO
SET XACT_ABORT OFF;
If you have XACT_ABORT ON there is no need to manually catch any errors, unless you are doing error logging. XACT_ABORT will cause all errors to doom the transaction and roll it back.
All you need is
SET XACT_ABORT ON;
BEGIN TRAN;
--do stuff
COMMIT;
If there is only one statement then you don't even need BEGIN TRAN; and COMMIT;
I accidentally ran into a situation that I didn't put Begin Transaction at the beginning of my stored procedure and just wrote Commit Transaction as you can see below
ALTER PROCEDURE dbo.spTest
AS
BEGIN
DECLARE #MyId INT=1
BEGIN TRY
UPDATE Test
SET
-- Id -- this column value is auto-generated
CharName = 'david'
WHERE id=4
--Just to test locking behavior
WHILE(1=1)
BEGIN
SET #MyId=2;
END
COMMIT TRANSACTION
END TRY
BEGIN CATCH
ROLLBACK TRANSACTION
END CATCH
END
I expected SQL Server to give me a run time error but it didn't happen. Of course I should mention that based on my test it didn't acquire any lock on the table due to the lack of Begin Transaction but what is the point of COMMIT TRANSACTION and ROLLBACK TRANSACTION in such a condition and why didn't SQL Server raise any error?
Edit:
if i remove while block and put WaitFor Sql raise error when reaches to COMMIT TRANSACTION
ALTER PROCEDURE dbo.spTest
AS
BEGIN
UPDATE Test
SET CharName = 'david'
WHERE id=4
PRINT 'waiting for a minute '
WAITFOR DELAY '00:00:10';
COMMIT TRANSACTION
END
Now i am receiving this error
The COMMIT TRANSACTION request has no corresponding BEGIN TRANSACTION
what is the point of COMMIT TRANSACTION and ROLLBACK TRANSACTION in such a condition?
There is no point in this case
and why didn't SQL Server raise any error?
I don't see any code that would raise an error. It would help if you could explain where and why you think an error should be raised
With regards to whatever you're actually doing here;
If the purpose of this proc is to hold a transaction open, you'd need something more like this:
ALTER PROCEDURE dbo.spTest
AS
BEGIN
BEGIN TRANSACTION
UPDATE Test
SET CharName = 'david'
WHERE id=4
--Endless loop
WHILE(1=1)
BEGIN
PRINT 'waiting for a minute inside a transaction. Try something from another session'
WAITFOR DELAY '00:01';
END
-- Transaction will actually never be committed
-- Because this line will never be reached
-- because it's preceded by an endless loop
COMMIT TRANSACTION
END
The TRY / CATCH is a bit of a distraction. I've removed it.
Sorry for the horrid title...it's not very easy to explain what I'm asking in one line.
I want to run two dependent DML queries in a loop...if either of the queries fail/throw an error...I want to rollback the transaction, exit the loop, and terminate the entire proc while throwing an error (so jobs will detect the failure). I think what I have is correct...but I have a few questions because I want to better understand how it works. I've read the Microsoft documentation...but I'm still unclear on some things.
I know by using SET XACT_ABORT ON; that it will handle the rollback for the tran. Does that mean I do not need to check for IF (##TRANCOUNT > 0) in the CATCH block?
The other question...the reason I have the TRY...CATCH block is because of the WHILE loop...I'm not sure if the failed transaction will also terminate the proc, so I'm forcing it with THROW?
Here's what I have: (Ignore the fact that it's an infinite loop, I'm not including the break logic to keep the example simple)
SET XACT_ABORT ON;
WHILE(1=1)
BEGIN
BEGIN TRY
BEGIN TRAN;
--DML Query 1
--DML Query 2
COMMIT TRAN;
END TRY
BEGIN CATCH
IF (##TRANCOUNT > 0)
ROLLBACK TRAN;
THROW;
END CATCH
END
UPDATE
Okay I'm trying to figure out how to test it myself, I was having a hard time figuring out how to test it, but I think I've got it now. So now I feel bad for posting the question :D
Here's the test I've got so far...I'll update it as I make changes to it. It appears that the IF (##TRANCOUNT > 0) in the CATCH block is not necessary, because when I remove the ROLLBACK and check for a transaction after a failure...there's no open transaction. However, if I leave it in...the IF statement resolves to true and it still runs the rollback without error.
SET NOCOUNT ON;
IF OBJECT_ID('dbo.ChadTestTable1') IS NOT NULL DROP TABLE dbo.ChadTestTable1; --SELECT * FROM dbo.ChadTestTable1
CREATE TABLE dbo.ChadTestTable1 (TestField VARCHAR(10) NOT NULL)
IF OBJECT_ID('dbo.ChadTestTable2') IS NOT NULL DROP TABLE dbo.ChadTestTable2; --SELECT * FROM dbo.ChadTestTable2
CREATE TABLE dbo.ChadTestTable2 (TestField VARCHAR(10) NOT NULL)
INSERT INTO dbo.ChadTestTable1 (TestField) VALUES ('Test1')
INSERT INTO dbo.ChadTestTable2 (TestField) VALUES ('Test1')
SET XACT_ABORT ON;
WHILE(1=1)
BEGIN
BEGIN TRY
BEGIN TRAN;
RAISERROR('Update first table',0,1) WITH NOWAIT;
UPDATE dbo.ChadTestTable1 SET TestField = 'Test3'
RAISERROR('Update second table',0,1) WITH NOWAIT;
UPDATE dbo.ChadTestTable2 SET TestField = NULL
RAISERROR('Updates done',0,1) WITH NOWAIT;
COMMIT TRAN;
END TRY
BEGIN CATCH
--It appears this isn't necessary...but if it's here, it still resolves to true and runs?
IF (##TRANCOUNT > 0)
BEGIN
RAISERROR('Rolling back transaction',0,1) WITH NOWAIT;
ROLLBACK TRAN;
END
RAISERROR('Throwing Error',0,1) WITH NOWAIT;
THROW;
END CATCH
RAISERROR('End of loop',0,1) WITH NOWAIT;
BREAK;
END
SELECT * FROM dbo.ChadTestTable1 ctt
SELECT * FROM dbo.ChadTestTable2 ctt
So I figured out my own answer. I was having a fairly difficult time trying to figure out how to test this myself which is why I posted up the question, but I eventually figured it out. Here's what I found...90% of it you can probably find in Microsoft Documentation, but I read that, and was still left confused, so I resulted to bruteforce testing.
Here's what I found that I was missing after reading the documentation:
XACT_ABORT ON - Not only does it rollback the transaction, but it will also throw a terminating error...aka, if it's on and a transaction is in a loop, and an error is thrown in the tran, it will rollback the tran, break the loop, and it will also end the proc and nothing after it will be run. The proc/script ends there.
Therefore...I don't need the TRY...CATCH block in order to run the THROW and I don't need to rollback because it will be done automatically. I'm happy to have found this because that simplifies my code quite a bit.
I created an adhoc which has lots of delete/insert action.
For data integrity, I would like to rollback all if any action goes wrong.
The following is the pseudo code:
BEGIN TRY
BEGIN TRANSACTION
INSERT INTO New_Table VALUES(......)
DELETE FROM Old_Table
COMMIT
END TRY
BEGIN CATCH
IF ##TRANCOUNT > 0
ROLLBACK
END CATCH
Is there any performance issue here? I will use this logic about 10 times for insert/delete data in this adhoc.
i have a try catch block in my sp with just a insert statement in the try. the catch check error code if it is pk violation, if it is then do update. but some times i get "The current transaction cannot be committed and cannot support operations that write to the log file. Roll back the transaction.
Uncommittable transaction is detected at the end of the batch. The transaction is rolled back." so i added xact_abort on, but then i keep getting "Transaction count after EXECUTE indicates a mismatching number of BEGIN and COMMIT statements." and i found this.
http://www.ashishsheth.com/post/2009/08/14/Set-XACT_ABORT-ON-and-TryCatch-block-in-Sql-Server-2005.aspx
if this true. will my catch code not run if there is a error in my try block with xact_abort on?
It is not true, at least with SQL SERVER 2008, that SET XACT_ABORT ON will cause an error to skip the CATCH block:
Here is the code I tried using the Northwind database
SET XACT_ABORT OFF
BEGIN TRY
SELECT 1, ##TRANCOUNT
BEGIN TRAN
UPDATE [dbo].[Categories]
SET Description='BLAH'
WHERE [CategoryID]=2
SELECT 2, ##TRANCOUNT
SELECT 1/0 as whoops
COMMIT
SELECT 3, ##TRANCOUNT
END TRY
BEGIN CATCH
SELECT 'In Catch. Error occured', 4, ##TRANCOUNT
IF (XACT_STATE()) = 0
BEGIN
SELECT
N'There is no transaction'
END;
IF (XACT_STATE()) = -1
BEGIN
SELECT
N'The transaction is in an uncommittable state.' +
'Rolling back transaction.'
ROLLBACK TRANSACTION;
END;
-- Test whether the transaction is committable.
IF (XACT_STATE()) = 1
BEGIN
SELECT
N'The transaction is committable.' +
'Committing transaction.'
COMMIT TRANSACTION;
END;
END CATCH
This will, obviously, force an error when it hits the SELECT 1/0 statement. With SET XACT_ABORT OFF, when the CATCH block is reached, the value returned by the XACT_STATE() function is 1, causing the code to run which COMMITs the transaction. When SET XACT_ABORT is on, the value returned, in the CATCH block is -1 so the code which ROLLs back the transaction is executed.
This is based on:
http://msdn.microsoft.com/en-us/library/ms175976.aspx
Let me add that, in that particular scenario (try insert, if PK violation then catch and update), it would be better to use IF EXISTS (select....) to see if the row is there and put your UPDATE statement there. Put your INSERT statement in ELSE block. Much cleaner.