Why would you commit a transaction within a catch clause? - sql-server-2005

Microsoft has the following example for try...catch in tsql:
USE AdventureWorks;
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
Source for above sample: Using TRY...CATCH in Transact-SQL
I don't understand why you'd want to commit a transaction that resulted in an exception. It seems like at least 9 times out of 10 you'd want to IF (XACT_STATE()) !=0 ROLLBACK TRANSACTION. Why would you want a partial success over a clean slate?

This sample is completely wrong. Is understandable to have a try-catch block to deal with a key duplicate and recover and do an alternate operation (perhaps an update instead of the insert). But to have a block of code the COMMITS in case of success, but leaves the transaction open in case of error, and, even more, silently swallows the error is just mind blowing. This piece of code is a big big can of worms.
On my site there is a sample procedure template that handles errors and transactions correctly, allowing for embedded transaction to recover and continue on error correctly. Typical examples where you'd want the handling to recover on case of error is batch processing: as you progress through the batch, you savepoint before each record then attempt to process. If processing fails, you save the record in a failed table and continue without loosing the whole batch.
Update
LOL, I missed the commit in the catch too. Then is not that bad as my original comment. I still prefer my template, that uses a savepoint and allows for nested transactions.

Related

will this approach work for executing ddl in a transaction?

I'm creating DDL for all of the objects in my db for propogation to the upper environments. I want to ensure an all or nothing transaction including all database object creates/alters and data load inserts. Is it as simple as this or are there any caveats?
BEGIN TRANSACTION
-- include create script generated from ssms
-- include insert data load scripts
COMMIT
I normally just use transactions for multi-table inserts etc.
Try this:
USE [DatabaseName]
GO
BEGIN TRANSACTION Tran1
BEGIN TRY
INSERT ...
COMMIT TRANSACTION Tran1
END TRY
BEGIN CATCH
-- ROLLBACK first.
ROLLBACK TRANSACTION Tran1
END CATCH
GO
Be advised that, if this fails and any of the tables that have identities where data was inserted but subsequently rolled back, then the identity values will need to be RESEEDED. For some reason, the incrementing of Identity values occurs outside of transactions.
Yes, you generally execute both DDL and DML within a transaction to ensure all-or-none. However, some DDL (e.g. CREATE DATABASE) cannot be executed in a transaction and others (e.g. CREATE SCHEMA) must be the only statement in a batch.
Barring those restrictions, you can use both transactions and structured error handling in your T-SQL script:
SET XACT_ABORT ON;
BEGIN TRY
BEGIN TRANSACTION;
-- include create script generated from ssms
-- include insert data load scripts
COMMIT;
END TRY
BEGIN CATCH
IF ##TRANCOUNT > 0 ROLLBACK;
THROW;
END;

SQL Server - Transaction is getting locked in case of error

BEGIN TRANSACTION;
BEGIN TRY
ALTER TABLE dbo.SomeLogs
ADD SomeID NVARCHAR(250) NULL
ALTER TABLE dbo.SomeLogs
ADD SomeID NVARCHAR(250) NULL
COMMIT TRANSACTION;
END TRY
BEGIN CATCH
ROLLBACK TRANSACTION;
DECLARE #Msg NVARCHAR(MAX);
SELECT #Msg = ERROR_MESSAGE();
RAISERROR('Error Occured: %s', 20, 101, #Msg) WITH LOG;
END CATCH;
Running the above query gives the following error which is correct as I am trying to add same column twice.
Msg 2705, Level 16, State 4, Line 6
Column names in each table must be unique. Column name 'SomeID' in table 'dbo.SomeLogs' is specified more than once.
But the problem is that the SomeLogs table is locked. When I try to do SELECT on SomeLogs I get this error.
Failed to retrieve data for this request. (Microsoft.SqlServer.Management.Sdk.Sfc)
For help, click: http://go.microsoft.com/fwlink?ProdName=Microsoft%20SQL%20Server&LinkId=20476
ADDITIONAL INFORMATION:
Lock request time out period exceeded. (Microsoft SQL Server, Error: 1222)
For help, click: http://go.microsoft.com/fwlink?ProdName=Microsoft%20SQL%20Server&ProdVer=11.00.3000&EvtSrc=MSSQLServer&EvtID=1222&LinkId=20476
Why is the catch block not catching this error? And how to avoid table getting locked?
Try..Catch
block will not catch this error because it's compilation error, and compilation errors cannot be catched within the current scope.
The table remains locked because locks are not released until transaction is committed or rolled back. When xact_abort is set to off(default for SSMS sessions) transaction is not rolled backed when the compilation error occurs, that is by (bad!) design, and to fix this you should use
set xact_abort on;
You can catch this error in outer scope, for example, if you wrap this code in stored procedure or dynamic code, executing sp/dynamic code within try..catch
Using TRY…CATCH with XACT_STATE
https://learn.microsoft.com/en-us/sql/t-sql/language-elements/try-catch-transact-sql
The following example shows how to use the TRY…CATCH construct to
handle errors that occur inside a transaction. The XACT_STATE function
determines whether the transaction should be committed or rolled back.
In this example, SET XACT_ABORT is ON. This makes the transaction
uncommittable when the constraint violation error occurs.
You can specify SET XACT_ABORT ON to automatically rollback the transaction in the event of an error or attention (i.e. client query cancel or timeout). The general TRY/CATCH pattern I suggest in stored procedures and batches in SQL 2012 and later:
SET XACT_ABORT ON;
BEGIN TRY
BEGIN TRAN;
--do stuff
COMMIT;
END TRY
BEGIN CATCH
IF ##TRANCOUNT > 0 ROLLBACK TRANSACTION;
THROW;
END CATCH;
See Erland Sommarskog's error handling article for detailed explanation.

Transaction count after EXECUTE indicates a mismatching number of BEGIN and COMMIT statements. Previous count = 1, current count = 0

I have an Insert stored procedure which will feed data to Table1 and get the Column1 value from Table1 and call the second stored procedure which will feed the Table2.
But when I call The second stored procedure as:
Exec USPStoredProcName
I get the following error:
Transaction count after EXECUTE indicates a mismatching number of BEGIN and COMMIT statements. Previous count = 1, current count = 0.
I have read the answers in other such questions and am unable to find where exactly the commit count is getting messed up.
If you have a TRY/CATCH block then the likely cause is that you are catching a transaction abort exception and continue. In the CATCH block you must always check the XACT_STATE() and handle appropriate aborted and uncommitable (doomed) transactions. If your caller starts a transaction and the calee hits, say, a deadlock (which aborted the transaction), how is the callee going to communicate to the caller that the transaction was aborted and it should not continue with 'business as usual'? The only feasible way is to re-raise an exception, forcing the caller to handle the situation. If you silently swallow an aborted transaction and the caller continues assuming is still in the original transaction, only mayhem can ensure (and the error you get is the way the engine tries to protect itself).
I recommend you go over Exception handling and nested transactions which shows a pattern that can be used with nested transactions and exceptions:
create procedure [usp_my_procedure_name]
as
begin
set nocount on;
declare #trancount int;
set #trancount = ##trancount;
begin try
if #trancount = 0
begin transaction
else
save transaction usp_my_procedure_name;
-- Do the actual work here
lbexit:
if #trancount = 0
commit;
end try
begin catch
declare #error int, #message varchar(4000), #xstate int;
select #error = ERROR_NUMBER(), #message = ERROR_MESSAGE(), #xstate = XACT_STATE();
if #xstate = -1
rollback;
if #xstate = 1 and #trancount = 0
rollback
if #xstate = 1 and #trancount > 0
rollback transaction usp_my_procedure_name;
raiserror ('usp_my_procedure_name: %d: %s', 16, 1, #error, #message) ;
end catch
end
go
I had this problem too. For me, the reason was that I was doing
return
commit
instead of
commit
return
in one stored procedure.
This normally happens when the transaction is started and either it is not committed or it is not rollback.
In case the error comes in your stored procedure, this can lock the database tables because transaction is not completed due to some runtime errors in the absence of exception handling
You can use Exception handling like below. SET XACT_ABORT
SET XACT_ABORT ON
SET NoCount ON
Begin Try
BEGIN TRANSACTION
//Insert ,update queries
COMMIT
End Try
Begin Catch
ROLLBACK
End Catch
Source
Be aware of that if you use nested transactions, a ROLLBACK operation rolls back all the nested transactions including the outer-most one.
This might, with usage in combination with TRY/CATCH, result in the error you described. See more here.
This can also occur if your stored procedure encounters a compile failure after opening a transaction (e.g. table not found, invalid column name).
I found i had to use 2 stored procedures a "worker" one and a wrapper one with try/catch both with logic similar to that outlined by Remus Rusanu. The worker catch is used to handle the "normal" failures and the wrapper catch to handle compile failure errors.
https://msdn.microsoft.com/en-us/library/ms175976.aspx
Errors Unaffected by a TRY…CATCH Construct
The following types of errors are not handled by a CATCH block when they occur at the same level of execution as the TRY…CATCH construct:
Compile errors, such as syntax errors, that prevent a batch from running.
Errors that occur during statement-level recompilation, such as object name resolution errors that occur after compilation because of deferred name resolution.
Hopefully this helps someone else save a few hours of debugging...
In my case, the error was being caused by a RETURN inside the BEGIN TRANSACTION. So I had something like this:
Begin Transaction
If (#something = 'foo')
Begin
--- do some stuff
Return
End
commit
and it needs to be:
Begin Transaction
If (#something = 'foo')
Begin
--- do some stuff
Rollback Transaction ----- THIS WAS MISSING
Return
End
commit
For me after extensive debugging the fix was a simple missing throw; statement in the catch after the rollback. Without it this ugly error message is what you end up with.
begin catch
if ##trancount > 0 rollback transaction;
throw; --allows capture of useful info when an exception happens within the transaction
end catch
I had the same error message, my mistake was that I had a semicolon at the end of COMMIT TRANSACTION line
Avoid using
RETURN
statement when you are using
BEGIN TRY
...
END TRY
BEGIN CATCH
...
END CATCH
and
BEGIN, COMMIT & ROLLBACK
statements in SQL stored procedures
I encountered this error once after omitting this statement from my transaction.
COMMIT TRANSACTION [MyTransactionName]
In my opinion the accepted answer is in most cases an overkill.
The cause of the error is often mismatch of BEGIN and COMMIT as clearly stated by the error. This means using:
Begin
Begin
-- your query here
End
commit
instead of
Begin Transaction
Begin
-- your query here
End
commit
omitting Transaction after Begin causes this error!
Make sure you don't have multiple transactions in the same procedure/query out of which one or more are left uncommited.
In my case, I accidentally had a BEGIN TRAN statement in the query
This can also depend on the way you are invoking the SP from your C# code. If the SP returns some table type value then invoke the SP with ExecuteStoreQuery, and if the SP doesn't returns any value invoke the SP with ExecuteStoreCommand
For me, the issue was that I forgot to add the output keyword following some output parameters of a SP call within the transaction.
The exact reason for this message is the rule that SQL Server implies: Transaction count should be same at the beginning and the end of execution of a procedure. In other terms, a procedure;
shouldn't commit/rollback a transaction that it didn't start. In this case, previous count displayed in the exception message would be greater zero, and current count is zero. Best way to prevent this is capturing transaction count (##TRANCOUNT) at the very beginning of the execution, and using transaction statements only if it is zero. The sample procedure below is a simplest "safe" structure against this type of mistake. If this procedure is called within an existing transaction, it won't begin a new transaction nor try to commit or rollback the "inherited" one. Instead, it just re-throws the same error to caller context. This is also a good practice to keep the real source procedure of the error.
should decide the fate (commit or rollback) of a transaction it started, before it's execution ends. In this case, current count would be greater than previous count.
I would highly recommend reading Erland Sommarskog's Error and Transaction Handling in SQL Server thoroughly
create or alter proc sp_err266
as
begin
set nocount on
set xact_abort on
declare #trancount int = ##trancount
if #trancount = 0
begin tran
begin try
raiserror('Raise an unexpected error...', 16, 1);
if XACT_STATE() = 1 and #trancount = 0
commit;
end try
begin catch
if XACT_STATE() <> 0 and #trancount = 0
rollback;
else
throw;
end catch
end
If you are having a code structure of something like:
SELECT 151
RETURN -151
Then use:
SELECT 151
ROLLBACK
RETURN -151
For me two begin transactions and multi rollback transaction causing this issue.
------------------------------------------------------------
BEGIN TRANSACTION
-- BEGING TRANSACTION
call of stored procedure -- ROLLBACK TRANASCTION
-- ROLLBACK TRANSACTION
ROLLBACK TRANSACTION
-----------------------------------------------------------
It can rollback only one time, it won't have multi rollback statements, also check the return statements which is causing the issue.
In nested procedures ROLLBACK should be used with care, detailed explanation here https://stackoverflow.com/a/74479802/6204480

Cannot roll back subtransaction. No transaction or savepoint of that name was found

i have a loop while in sql which do something as it
begin tran one
do some inserts in others tables
--start loop
begin tran two
--do something
begin try
--if something fail then a trigger does rollback and this return a error (and this goes to catch), then don't i need do the rollbak in catch? this could not be dissable because this is working on production
--something finished ok
commit tran two
end try
begin catch
rollback tran two
end catch
--finished loop
commit
----------
i got this error
Uncommittable transaction is detected at the end of the batch. The
transaction is rolled back.
begin tran one
begin tran two
rollback tran two
doing this code i get this:
Cannot roll back two. No transaction or savepoint of that name was found.
I only want the subquery to rollback the second loop and continue with others records.
Operator rollback rolls back all transaction, for roll back only second loop you you must use savepoints:
begin tran one
-- do some inserts in others tables
--start loop
save tran two -- begin tran two
--do something
begin try
update product set id = 1 --if something fail then a trigger does rollback and this return a error (and this goes to catch), then don't i need do the rollbak in catch? this could not be dissable because this is working on production
--something finished ok
commit tran two
end try
begin catch
rollback tran two
end catch
--finished loop
commit
trigger example:
create table product (id int)
GO
create trigger product_trigger on product for update
as
set xact_abort off
if (select count(*) from inserted i join product p on i.id=p.id)=0 begin
if (##trancount>0) begin
/* rollback */
raiserror('product does not exist', 16, 1)
end
end
In my case, was my code was calling, thru an EF DbContext method, a SQL Server stored procedure, which contained a non-nested transaction.
Since, as #NotMe has already pointed-out, that "there is no such as a nested transaction in SQL Server", I began wondering whether my process was really transaction-nestingless.
Suspecting, that my DbContext had some guilt, I started checking on DbContext options, until DbContext.Configuration.EnsureTransactionsForFunctionsAndCommands = True caught my attention.
So, as soon as I changed it value to True, everything worked successfully.
MyDbContext.Configuration.EnsureTransactionsForFunctionsAndCommands = false;
What happened?
Well, in my opinion, EF's ObjectContext.ExecuteFunction method was managing its own outer transaction as a wrapper to my stored procedure's inner transaction, so, when my stored procedure's ROLLBACK TRAN was hit, there was no pending transaction when EF's COMMIT/ROLLBACK code was hit.
Oddly enough, while gathering some references on EnsureTransactionsForFunctionsAndCommands property, I found that this default behaviour is due to one of the worst (in my opinion) EF team's decision ever, since it collides diretly with every ROLLBACK TRAN inside a T-SQL script.
For further details on EF, check insightfull SO's QA at EF6 wraps every single stored procedure call in its own transaction. How to prevent this?
Basically, everyone should check ##trancount > 0 before issuing a ROLLBACK command, whether named or not, specially inside stored procedure.
CREATE PROCEDURE Proc1 AS
BEGIN
BEGIN TRAN
EXEC Proc2
IF(##trancount > 0)
COMMIT TRAN
END
CREATE PROCEDURE Proc2 AS
BEGIN
BEGIN TRAN
ROLLBACK TRAN
END
For better awareness about Microsoft SQL Server's nested transactions, I would suggest reading the following article
Be careful using ROLLBACK on nested transaction in SQL Server!
Hope it helps someone :-)

What is the benefit of using "SET XACT_ABORT ON" in a stored procedure?

What is the benefit of using SET XACT_ABORT ON in a stored procedure?
SET XACT_ABORT ON instructs SQL Server to rollback the entire transaction and abort the batch when a run-time error occurs. It covers you in cases like a command timeout occurring on the client application rather than within SQL Server itself (which isn't covered by the default XACT_ABORT OFF setting.)
Since a query timeout will leave the transaction open, SET XACT_ABORT ON is recommended in all stored procedures with explicit transactions (unless you have a specific reason to do otherwise) as the consequences of an application performing work on a connection with an open transaction are disastrous.
There's a really great overview on Dan Guzman's Blog,
In my opinion SET XACT_ABORT ON was made obsolete by the addition of BEGIN TRY/BEGIN CATCH in SQL 2k5. Before exception blocks in Transact-SQL it was really difficult to handle errors and unbalanced procedures were all too common (procedures that had a different ##TRANCOUNT at exit compared to entry).
With the addition of Transact-SQL exception handling is much easier to write correct procedures that are guaranteed to properly balance the transactions. For instance I use this template for exception handling and nested transactions:
create procedure [usp_my_procedure_name]
as
begin
set nocount on;
declare #trancount int;
set #trancount = ##trancount;
begin try
if #trancount = 0
begin transaction
else
save transaction usp_my_procedure_name;
-- Do the actual work here
lbexit:
if #trancount = 0
commit;
end try
begin catch
declare #error int, #message varchar(4000), #xstate int;
select #error = ERROR_NUMBER(), #message = ERROR_MESSAGE(), #xstate = XACT_STATE();
if #xstate = -1
rollback;
if #xstate = 1 and #trancount = 0
rollback
if #xstate = 1 and #trancount > 0
rollback transaction usp_my_procedure_name;
raiserror ('usp_my_procedure_name: %d: %s', 16, 1, #error, #message) ;
end catch
end
go
It allows me to write atomic procedures that rollback only their own work in case of recoverable errors.
One of the main issues Transact-SQL procedures face is data purity: sometimes the parameters received or the data in the tables are just plain wrong, resulting in duplicate key errors, referential constrain errors, check constrain errors and so on and so forth. After all, that's exactly the role of these constrains, if these data purity errors would be impossible and all caught by the business logic, the constrains would be all obsolete (dramatic exaggeration added for effect). If XACT_ABORT is ON then all these errors result in the entire transaction being lost, as opposed to being able to code exception blocks that handle the exception gracefully. A typical example is trying to do an INSERT and reverting to an UPDATE on PK violation.
Quoting MSDN:
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.
In practice this means that some of the statements might fail, leaving the transaction 'partially completed', and there might be no sign of this failure for a caller.
A simple example:
INSERT INTO t1 VALUES (1/0)
INSERT INTO t2 VALUES (1/1)
SELECT 'Everything is fine'
This code would execute 'successfully' with XACT_ABORT OFF, and will terminate with an error with XACT_ABORT ON ('INSERT INTO t2' will not be executed, and a client application will raise an exception).
As a more flexible approach, you could check ##ERROR after each statement (old school), or use TRY...CATCH blocks (MSSQL2005+). Personally I prefer to set XACT_ABORT ON whenever there is no reason for some advanced error handling.
Regarding client timeouts and the use of XACT_ABORT to handle them, in my opinion there is at least one very good reason to have timeouts in client APIs like SqlClient, and that is to guard the client application code from deadlocks occurring in SQL server code. In this case the client code has no fault, but has to protect it self from blocking forever waiting for the command to complete on the server. So conversely, if client timeouts have to exist to protect client code, so does XACT_ABORT ON has to protect server code from client aborts, in case the server code takes longer to execute than the client is willing to wait for.
It is used in transaction management to ensure that any errors result in the transaction being rolled back.
Adding new updates here. The latest MSDN update shows how to use both XACT_ABORT ON and TRY/CATCH Block. MSDN Link
-- Check to see whether this stored procedure exists.
IF OBJECT_ID (N'usp_GetErrorInfo', N'P') IS NOT NULL
DROP PROCEDURE usp_GetErrorInfo;
GO
-- Create procedure to retrieve error information.
CREATE PROCEDURE usp_GetErrorInfo
AS
SELECT
ERROR_NUMBER() AS ErrorNumber
,ERROR_SEVERITY() AS ErrorSeverity
,ERROR_STATE() AS ErrorState
,ERROR_LINE () AS ErrorLine
,ERROR_PROCEDURE() AS ErrorProcedure
,ERROR_MESSAGE() AS ErrorMessage;
GO
-- SET XACT_ABORT ON will cause the transaction to be 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 statement succeeds, commit the transaction.
COMMIT TRANSACTION;
END TRY
BEGIN CATCH
-- Execute error retrieval routine.
EXECUTE usp_GetErrorInfo;
-- Test XACT_STATE:
-- If 1, the transaction is committable.
-- If -1, the transaction is uncommittable and should
-- be rolled back.
-- XACT_STATE = 0 means that 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
N'The transaction is in an uncommittable state.' +
'Rolling back transaction.'
ROLLBACK TRANSACTION;
END;
-- Test whether the transaction is committable.
-- You may want to commit a transaction in a catch block if you want to commit changes to statements that ran prior to the error.
IF (XACT_STATE()) = 1
BEGIN
PRINT
N'The transaction is committable.' +
'Committing transaction.'
COMMIT TRANSACTION;
END;
END CATCH;
GO