USE AdventureWorks;
GO
BEGIN TRANSACTION;
GO
DELETE FROM HumanResources.JobCandidate WHERE JobCandidateID = 10;
DELETE FROM HumanResources.JobCandidate WHERE JobCandidateID = 11;
DELETE FROM HumanResources.JobCandidate WHERE JobCandidateID = 12;
GO
COMMIT TRANSACTION;
GO
What happens if the first delete statement fails? Will the 2nd and 3rd delete statements be executed? The example doesn't have any error handling, will it leave an open transaction in the case of an exception, or will SQL Server rollback the transaction automatically? Open transaction = locked resources, right?
I am deciding whether I must apply TRY...CATCH to stored procedures that use transactions.
I am aware about set xact_abort on, but want to know what happens without it.
Here is what I found in docs - Controlling Transactions (Database Engine):
If an error prevents the successful completion of a transaction, SQL Server automatically rolls back the transaction and frees all resources held by the transaction
However I read in other posts that automatic rollback is not fired.
In your example, without the use of SET XACT_ABORT ON, the transaction will continue and commit even if the first statement fails. In the text you quoted, the key words are if an error **prevents** the successful completion of a transaction, and a DELETE statement failing does not prevent the transaction from completing.
An example of an error that would cause an automatic rollback is if the connection to the database was severed in the middle of a transaction. Further down the MSDN article you referenced says:
If a run-time statement error (such as a constraint violation) occurs
in a batch, the default behavior in the Database Engine is to roll
back only the statement that generated the error. You can change this
behavior using the SET XACT_ABORT statement. After SET XACT_ABORT ON
is executed, any run-time statement error causes an automatic rollback
of the current transaction. Compile errors, such as syntax errors, are
not affected by SET XACT_ABORT.
It's always a good idea to use error handling to catch errors and rollback if needed.
I prefer to control the process manually:
BEGIN TRY
BEGIN TRAN
-- do work
COMMIT
END TRY
BEGIN CATCH
ROLLBACK
RAISERROR (...)
END CATCH
GO
Related
I currently have an audit process that is part of all procedures and it is very basic at a high level.
Procedure is executed.
It inserts into the Audit table with a start time and status of Running.
It completes with out errors and then updates that record to show a status of Complete.
If there is an actual error it will update with a status of Error.
However if a user manually aborts the procedures via the SQL Server Management Studio no update will occur. The record will remain as "Running".
Is there any functionality I can use to capture this scenario so I can tie out the audit record?
You simply need to enclose it inside a transaction, and use XACT_ABORT for automatic rollback.
CREATE OR ALTER PROCEDURE YourProc
-- parameters here
AS
SET NOCOUNT, XACT_ABORT ON;
BEGIN TRAN;
-- everything else here
COMMIT;
Always use SET XACT_ABORT ON if you have a transaction. This ensures that if the code is aborted or it errors, the transaction will be rolled back and not left hanging.
Do not use TRY CATCH unless you want to actually handle the error and log it. It is not necessary to use CATCH to ensure a rollback if you have XACT_ABORT. If you do catch, then rethrow the error using THROW; rather than RAISERROR. For example:
BEGIN CATCH
IF XACT_STATE() <> 0
ROLLBACK;
INSERT Audit... ;
THROW;
END CATCH;
But note that certain errors cannot be caught, and a user abort cannot be caught either. I suggest you use XEvents for that .
I assumed uncaught throw inside a transaction will roll it back. But testing with this code, seems the transaction stays open. Is this normal behavior?
begin transaction;
throw 50001, N'Exception', 1;
commit transaction;
I use this query:
select * from sys.sysprocesses where open_tran = 1;
to list transactions and see 1 open. Once I run commit on that connection, it closes.
So, when I throw do I need to always rollback myself before? And what if some other code throws in my transaction but outside my code?
Normally it's not an issue as closing the connection ends it. But if I sp_getapplock bound to transaction, it stays locked if I throw without manual rollback.
I think you misunderstand the trow here. As you have written it, it leaves the connection opened because the commit transaction; is not executed. The begin transaction; (increments the ##TRANCOUNT counter), but throw does not automatically decrese it! (no automatic rollback).
When throw is executed it tries to find a catch statement if not found only error is shown and the the rest is terminated.
To quote the MSDN:
If a TRY...CATCH construct is not available, the statement batch is
terminated. The line number and procedure where the exception is
raised are set. The severity is set to 16.
In your simple case you could extend it like this:
BEGIN TRY
begin transaction;
throw 50001, N'Exception', 1;
END TRY
BEGIN CATCH
IF <boolean_expression_for_commit>
commit transaction;
ELSE
rollback transaction;
END CATCH
That will depend on your usecase. Both commit and rollback decrement the ##TRANCOUNT counter. You will get your transaction closed based on the <boolean_expression_for_commit>.
EDIT The OP wanted to know more about XACT_ABORT
If the XACT_ABORT is set to ON the TROW causes rollback on the whole transaction even when CATCH is missing. That will decrese the transaction counter and close the open transaction.
There is a catch however. If a developer wants to create a log in a CATCH then rollback is performed also on the CATCH block! (no log is created)
Things can get weird when SET XACT_ABORT OFF (the default). It may or may not leave the transaction opened based on severity of the error. If the error is considered sereve enough it will still rollback. In your case a simple THROW is not severe enough.
Note: That is also a reason why now THROW should be used instread of RAISERROR. The THROW follows the XACT_ABORT setting, RAISEERROR ignores it.
I have an issue with an AFTER INSERT UPDATE TRIGGER. Test code below. The problem is that when a transaction on this table is started it's rolled back.
After a lot of research online, it seems that everyone is in aggreance that that is NOT the case. Regardless, is there something on server side setting I'm missing?
I have also added the sp_settriggerorder() to the first trigger, naming the one in question last.
I've also noted that disabling the below trigger allows the transaction to complete.
Severity below 11 is not an option because it prompts an ugly warning in the client application to forces the user to "Expand" the selection.
Severity 11 though 16 all give me this issue.
USE [Test]
GO
/****** Object: Trigger [dbo].[co_bln_AfterIup] Script Date: 06/29/2017 14:43:12 ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER TRIGGER [dbo].[co_bln_AfterIup]
ON [dbo].[co_bln]
AFTER INSERT,UPDATE
AS
IF 1 = 1
RAISERROR('test error',16,1)
Short answer
1# Set SET XACT_ABORT OFF at the beginning of trigger execution
2# Do some DMLs(inserts/updates/deletes)
3# Set condition to fire RAISERROR with the message and severity under 18
4# Results would be committed transaction with raised error
Longer answer
There are two ways of handling errors in SQL Server ,one is using RAISERROR and another is using THROW. Which one you prefer would depend on a further explanation
Using throw you cannot specify severity, where as using RAISERROR you can
Using a higher(above 18) severity you can kill a user connection, which is not something you can do with throw
Also RAISERROR can be specified within explicit transaction (BEGIN/END) whereas
THROW cannot(Have to use TRY/CATCH block).
Whats important here is something that connects these two error handlers is XACT_ABORT
By Specifying XACT_ABORT you are defining a transaction behavior
It is possible within a single transaction to have certain transaction that will fail, and some of them wont, depending on XACT_ABORT whether is ON or OFF , some of these succeeded transaction might be executed while others wont, which is usually something you dont want to do if you want to keep consistent database
More about XACT_ABORT you can read here
Now how is this related to your error handlers?
If you specify at the beginning of transaction SET XACT_ABORT OFF, your RAISERROR error handler wont rollback transaction, and whatever it is that you was doing before will be reflected to the database.
You have to options to prevent this , using explicit ROLLBACK transaction or Enabling XACT_ABORT
If you want to use TRY/CATCH block, and do some error handling you can use either one of these. Whenever you call RAISERROR or THROW you will be directly transferred to the CATCH block. However remember that even if you are in the catch block if XACT_ABORT is OFF and you are using RAISERROR with no ROLLBACK command, that transaction will be completed regardless.
If you use THROW it will be rolledback immediately
However XACT_ABORT on or off has no effect on THROW handler and will be executed as expected( with rolling back all the changes)
Note also that even with XACT_ABORT OFF you can still rollback transaction using severity over 18
Therefore depending on what you are trying to achieve you can pick the one that suits you the best. Following the standard, THROW is the one to go, and its newer but if you wanted to just display the warning - back to Short answer
You could surround the statements around the RAISERROR in a TRY/CATCH block if you don't want the RAISERROR to end the transaction. Or you could surround the INSERT/UPDATE statement that fires the trigger with a TRY/CATCH block.
From RAISERROR documentation (Remarks):
When RAISERROR is run with a severity of 11 or higher in a TRY block, it transfers control to the associated CATCH block.
RAISERROR with a severity between 11 and 20 should transfer control to the catch block and should not necessarily abort the transaction if you don't write is as such.
Without seeing all your code, I'm guessing that some part of your program is rolling the transaction back as a response to the error raised in the trigger.
I am having a bit of hard time understanding how errors affect the completion of batches and/or transactions.
For instance:
BEGIN TRAN;
SELECT 1/0 AS Error;
ROLLBACK;
BEGIN TRAN;
SELECT 1/1 AS NOError;
COMMIT;
GO
Should not the second transaction succeed even though the first fails? Are not transactions dealt with on one-by-one basis? And what is the role played by batches here?
I was reading about SET XACT_ABORT ON command, and the MSDN says:
When SET XACT_ABORT is ON, if a Transact-SQL statement raises a run-time error, the entire transaction is terminated and rolled back.
If it only fails the containing transaction, why the second transaction is never reached?
I have a stored procedure which is called inside a trigger on Insert/Update/Delete.
The problem is that there is a certain code block inside this SP which is not critical.
Hence I want to ignore any erros arising from this code block.
I inserted this code block inside a TRY CATCH block. But to my surprise I got the following error:
The current transaction cannot be committed and cannot support operations that write to the log file. Roll back the transaction.
Then I tried using SAVE & ROLLBACK TRANSACTION along with TRY CATCH, that too failed with the following error:
The current transaction cannot be committed and cannot be rolled back to a savepoint. Roll back the entire transaction.
My server version is: Microsoft SQL Server 2008 (SP2) - 10.0.4279.0 (X64)
Sample DDL:
IF OBJECT_ID('TestTrigger') IS NOT NULL
DROP TRIGGER TestTrigger
GO
IF OBJECT_ID('TestProcedure') IS NOT NULL
DROP PROCEDURE TestProcedure
GO
IF OBJECT_ID('TestTable') IS NOT NULL
DROP TABLE TestTable
GO
CREATE TABLE TestTable (Data VARCHAR(20))
GO
CREATE PROC TestProcedure
AS
BEGIN
SAVE TRANSACTION Fallback
BEGIN TRY
DECLARE #a INT = 1/0
END TRY
BEGIN CATCH
ROLLBACK TRANSACTION Fallback
END CATCH
END
GO
CREATE TRIGGER TestTrigger
ON TestTable
FOR INSERT, UPDATE, DELETE
AS
BEGIN
EXEC TestProcedure
END
GO
Code to replicate the error:
BEGIN TRANSACTION
INSERT INTO TestTable VALUES('data')
IF ##ERROR > 0
ROLLBACK TRANSACTION
ELSE
COMMIT TRANSACTION
GO
I was going through the same torment, and I just solved it!!!
Just add this single line at the very first step of your TRIGGER and you're going to be fine:
SET XACT_ABORT OFF;
In my case, I'm handling the error feeding a specific table with the batch that caused the error and the error variables from SQL.
Default value for XACT_ABORT is ON, so the entire transaction won't be commited even if you're handling the error inside a TRY CATCH block (just as I'm doing). Setting its value for OFF will cause the transaction to be commited even when an error occurs.
However, I didn't test it when the error is not handled...
For more info:
SET XACT_ABORT (Transact-SQL) | Microsoft Docs
I'd suggest re-architecting this so that you don't poison the original transaction - maybe have the transaction send a service broker message (or just insert relevant data into some form of queue table), so that the "non-critical" part can take place in a completely independent transaction.
E.g. your trigger becomes:
CREATE TRIGGER TestTrigger
ON TestTable
FOR INSERT, UPDATE, DELETE
AS
BEGIN
INSERT INTO QueueTable (Col1,Col2)
SELECT COALESCE(i.Col1,d.Col1),COALESCE(i.Col2,d.Col2) from inserted i,deleted d
END
GO
You shouldn't do anything inside a trigger that might fail, unless you do want to force the transaction that initiated the trigger action to also fail.
This is a very similar question to Why try catch does not suppress exception in trigger
Also see the answer here T-SQL try catch transaction in trigger
I don’t think you can use savepoints inside a trigger. I mean, you can but I googled about it and I saw a few people saying that they don’t work. If you replace your “save transaction” for a begin transaction, it compiles. Of course it is not necessary because you have the outer transaction control and the inner rollback would rollback everything.