TSQL - Uncaught throw in transaction does not rollback? - sql-server

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.

Related

Is it possible to execute code in SQL Server when a user aborts a procedure?

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 .

Difference between batch and transaction in SQL Server in terms of handling errors

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?

Is ROLLBACK TRANSACTION required?

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

Do I need to use a try..catch block, and explicit rollback in a SQL Server procedure?

If I am coding a SQL Server (2008r2) procedure, and I wrap it in a transaction, do I need to explicitly enclose it in a try..catch block, and then explicitly call rollback in the catch block, or will it exit and rollback the same on its own?
i.e.:
How does this:
begin transaction
begin try
delete from....
insert into...
end try
begin catch
rollback transaction
return
end catch
commit transaction
Compare with:
begin transaction
delete from....
insert into...
commit transaction
Thank you for any help.
The answer to your question depends on the SET XACT_ABORT setting:
Specifies whether SQL Server automatically rolls back the current
transaction when a Transact-SQL statement raises a run-time error.
When SET XACT_ABORT is ON, if a Transact-SQL statement raises a
run-time error, the entire transaction is terminated and rolled back.
When SET XACT_ABORT is OFF, in some cases only the Transact-SQL
statement that raised the error is rolled back and the transaction
continues processing. Depending upon the severity of the error, the
entire transaction may be rolled back even when SET XACT_ABORT is OFF.
OFF is the default setting.
Compile errors, such as syntax errors, are not affected by SET
XACT_ABORT.
For example, try the following code. The first division by 0 raises an error but continues execution. The second division by zero raises an error and halts execution:
begin transaction
set xact_abort off
select 1 / 0 -- causes divide by zero error, but continues
select ##trancount -- returns 1
set xact_abort on
select 1 / 0 -- causes divide by zero error and terminates execution
select ##trancount -- we never get here
rollback
If XACT_ABORT is ON, then errors will abort the transaction, and you don't need a TRY / CATCH.
If XACT_ABORT is OFF, you will need to check the status of each statement to see if an error occurred:
begin transaction
delete from...
if ##error <> 0
begin
if ##trancount > 0
rollback
return
end
insert into...
if ##error <> 0
begin
if ##trancount > 0
rollback
return
end
commit
However, if you ever find a case where you need to TRY / CATCH, you may need to do something special when the error occurs. If so, don't forget to TRY / CATCH the exception handling:
begin transaction
set xact_abort on
begin try
select 1 / 0 -- causes divide by zero error and terminates execution
select ##trancount -- we never get here
commit
end try
begin catch
select xact_state() -- this will be -1 indicating you MUST rollback before doing any other operations
select ##trancount -- this will probably be one, because we haven't ended the transaction yet
if xact_state() <> 0
begin try
select 'rollback'
rollback
-- do something to handle or record the error before leaving the current scope
select 'exception processing here'
--insert into...
end try
begin catch
-- ignore rollback errors
end catch
end catch
rollbacks will occur automatically if there is an error IN MOST CASES BUT NOT ALL
if you want to guarantee a rollback for all errors precede the begin transaction with SET XACT_ABORT ON
Best practice is to explicity catch errors with a try-catch block and take action there, including perhaps a rollback and reporting/logging the error.
It depends on the severity level of the error. Sufficiently high -- 16, perhaps? -- the process may stop at the failing line, leaving the transaction open and your locks in place. If there's any chance of error within a transaction, you definitely want to wrap it in the try-catch block, as you did in your first example.

What happens here? SQL Server - XACT_ABORT ON + ##ERROR Checking . .

What happens with this type of scenario?
SET XACT_ABORT ON
BEGIN TRANSACTION
---DO SOMETHING HERE THAT CAUSES AN ERROR
COMMIT TRANSACTION
if ##error != 0
raiserror('SP failed. Step 7.', 20, -1) with log GO
My guess is that because XACT_ABORT is ON the COMMIT TRANSACTION never happens (because the whole thing is rolled back and terminated), and neither does the last statement (checking for ##error and then calling raiseerror).
Correct.
SET XACT_ABORT jumps out of the batch. Your IF is part of the same batch.
If you want some processing on error, then use BEGIN TRY etc
SET XACT_ABORT ON
BEGIN TRY
BEGIN TRANSACTION
---DO SOMETHING HERE THAT CAUSES AN ERROR
COMMIT TRANSACTION
END TRY
BEGIN CATCH
raiserror('SP failed. Step 7.', 20, -1) with log
END CATCH
GO
I'm also intrigued by severity 20 because it breaks the connection. Usually you'd use 16 which is user defined error.

Resources