SQL transaction commit and error handle - sql-server

My code is as following:
create my sp
as
declare cursor
open cursor
fetch next from
begin try
begin transaction tran1
exec sp_1....
commit transaction tran1
select * from table
begin transaction tran2
exec sp_2....
commit transaction tran2
end
fetch next from cursor
end try
begin catch
SELECT ERROR_MESSAGE() AS ErrorMessage;
IF ##TRANCOUNT > 0
ROLLBACK TRAN;
fetch next from cursor
end catch
close cursor
deallocate cursor
I need this code to :
contain two transactions and rollback when error happened
when the stored procedure gets killed, the transaction will still rollback
I think that the catch part may not right, because there are two transaction and needs to be specific.

Related

What is the correct template to use with try catch and transactions?

I am providing 4 examples below. My question is - which is the appropriate template to use when dealing with try/catch and transactions.
Example 1
TSQL Try / Catch within Transaction or vice versa?
The accepted answer for the above has the following structure:
BEGIN TRY
BEGIN TRANSACTION SCHEDULEDELETE
// do something
COMMIT TRANSACTION SCHEDULEDELETE
PRINT 'Operation Successful.'
END TRY
BEGIN CATCH
IF (##TRANCOUNT > 0)
BEGIN
ROLLBACK TRANSACTION SCHEDULEDELETE
PRINT 'Error detected, all changes reversed'
END
//may be print/log/throw error
END CATCH
Example 2
https://learn.microsoft.com/en-us/sql/t-sql/language-elements/try-catch-transact-sql?view=sql-server-ver15#b-using-trycatch-in-a-transaction
The structure given is:
BEGIN TRANSACTION;
BEGIN TRY
-- Generate a constraint violation error.
DELETE FROM Production.Product
WHERE ProductID = 980;
END TRY
BEGIN CATCH
//may be print/log/throw error
IF ##TRANCOUNT > 0
ROLLBACK TRANSACTION;
END CATCH;
IF ##TRANCOUNT > 0
COMMIT TRANSACTION;
GO
Example 3
https://learn.microsoft.com/en-us/sql/t-sql/language-elements/try-catch-transact-sql?view=sql-server-ver15#c-using-trycatch-with-xact_state
Microsoft also recommends using XACT_ABORT.
SET XACT_ABORT ON;
BEGIN TRY
BEGIN TRANSACTION;
//do something
COMMIT TRANSACTION;
END TRY
BEGIN CATCH
-- 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;
Example 4
This is what I use personally
BEGIN TRY
BEGIN TRANSACTION
//do something
COMMIT TRANSACTION
END TRY
BEGIN CATCH
IF (##TRANCOUNT > 0)
BEGIN
ROLLBACK TRANSACTION
END ;
//may be print/log/throw error
END CATCH
To summarize:
Example 1 - Transaction is inside the try block
Example 2 - Try is inside the transaction block, COMMIT is after the catch block
Example 3 - Transaction is inside the try block with xact_abort ON functionality
Example 4 - Transaction is inside the try block, COMMIT is inside and as the last line of the try block
Is my approach (example 4) the correct way to handle try/catch and transaction in SQL. If not, then why not and which example should I use to refactor my code from example 4?
according to the documentation
TRY...CATCH constructs do not trap the following conditions:
Warnings or informational messages that have a severity of 10 or
lower.
Errors that have a severity of 20 or higher that stop the SQL Server
Database Engine task processing for the session. If an error occurs
that has severity of 20 or higher and the database connection is not
disrupted, TRY...CATCH will handle the error.
Attentions, such as client-interrupt requests or broken client
connections.
When the session is ended by a system administrator by using the KILL
statement.
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.
Object name resolution errors
These errors are returned to the level that ran the batch, stored
procedure, or trigger.
so taking this fact into account, let's consider this examples.
Exaple 1: If we try to delete a row from a non-existent table, the session will remain with an open transaction, which will preserve resource locks and prevent VLFs from being reused in the transaction log for a simple recovery model.
BEGIN TRY
BEGIN TRANSACTION SCHEDULEDELETE
Delete From dbo.NoExists Where ID=1
COMMIT TRANSACTION SCHEDULEDELETE
END TRY
BEGIN CATCH
IF (##TRANCOUNT > 0)
BEGIN
ROLLBACK TRANSACTION SCHEDULEDELETE
END
END CATCH
Invalid object name "dbo.NoExists".
Select ##TRANCOUNT
-- 1
Example 2: The same problem as in example 1.
BEGIN TRANSACTION;
BEGIN TRY
DELETE FROM dbo.NoExists Where ID=1
END TRY
BEGIN CATCH
IF ##TRANCOUNT > 0
ROLLBACK TRANSACTION;
END CATCH;
IF ##TRANCOUNT > 0
COMMIT TRANSACTION;
Invalid object name "dbo.NoExists".
Select ##TRANCOUNT
-- 1
Example 3: In this example, XACT_ABORT ON will rollback the transaction if CATCH block doesn't handle this error and, therefore, doesn't rollback the transaction.
SET XACT_ABORT ON;
BEGIN TRY
BEGIN TRANSACTION;
DELETE FROM dbo.NoExists Where ID=1
COMMIT TRANSACTION;
END TRY
BEGIN CATCH
-- 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;
IF (XACT_STATE()) = 1
BEGIN
PRINT
N'The transaction is committable.' +
'Committing transaction.'
COMMIT TRANSACTION;
END;
END CATCH;
Invalid object name "dbo.NoExists".
Select ##TRANCOUNT
-- 0

How to Try-Catch or return a success flag when Transaction commits or rollbacked - SQL Server

I have a SQL Transaction in a Stored procedure , I want to know how to return a success message in case of Transaction successfully commits or failed at any point ie. rollbacked,I want to use that flag in my code in MVC C# .
Below code might help you on this,
BEGIN TRY
BEGIN TRANSACTION
-- sql update\delete statement goes here
COMMIT TRANSACTION
END TRY
BEGIN CATCH
IF (##TRANCOUNT > 0)
BEGIN
ROLLBACK TRANSACTION
PRINT 'Error ...'
END
SELECT
ERROR_NUMBER() AS Error#,
ERROR_MESSAGE() AS ErrorMsg
END CATCH
You can write as :
declare #success bit = 1
begin transaction
begin try
--main content of script here
end try
begin catch
rollback transaction
set #success = 0
end catch
if(#success = 1)
begin
commit transaction
end

set xact_abort on and try catch together

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.

How to rollback a transaction in a stored procedure?

Looking at the SQL Server Books Online, Microsoft seems to have an (incorrect) method of handling nested transactions in a stored procedure:
Nesting Transactions
Explicit transactions can be nested. This is primarily intended to support transactions in stored procedures that can be called either from a process already in a transaction or from processes that have no active transaction.
The example goes on to show a stored procedure that starts its own transaction ("The procedure enforces its transaction regardless of the transaction mode of any process that executes it."):
CREATE PROCEDURE TransProc #PriKey INT, #CharCol CHAR(3) AS
BEGIN TRANSACTION InProc
...
COMMIT TRANSACTION InProc;
This procedure can then either be called without a transaction running:
EXECUTE TransProc 3,'bbb';
Or with an explicit transaction:
BEGIN TRANSACTION OutOfProc;
EXEC TransProc 1, 'aaa';
COMMIT TRANSACTION OutOfProc
What they don't address is what happens when the stored produre:
fails with an error, but leaves the transaction running
fails with an error, but doesn't leave the transaction running
encounters an error, but continues executing with the transaction open
encounters an error, but continues executing with the transaction rolled back
There is no:
SET XACT_ABORT ON
##TRANCOUNT
anywhere in the canonical example.
If i didn't know any better, i would have thought that the line:
The following example shows the intended use of nested transactions.
should actually read
The following example shows the how not to use nested transactions.
Unless someone can make heads or tails of this BOL example?
You need to use the try catch block with the transaction. So in case you get the error in your catch block then you can rollback your transaction.
Please see the below sql server code for that.
BEGIN TRANSACTION;
BEGIN TRY
-- Some code
COMMIT TRANSACTION;
END TRY
BEGIN CATCH
ROLLBACK TRANSACTION;
END CATCH;
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

Basic template for Transactions in sqlserver

If I simply wrap my query with:
BEGIN TRANSACTION
COMMIT TRANSACTION
If anything fails inside of that, will it automatically rollback?
From looking at other code, they seem to check for an error, if there is an error then they do a GOTO statement which then calls ROLLBACK TRANSACTION
But that seems like allot of work, to have to check for IF( ##ERROR <> 0) after every insert/update.
I typically do something like this inside my stored procedures. It keeps things nice and safe and passes along any errors that I encounter.
SET XACT_ABORT ON;
BEGIN TRY
BEGIN TRANSACTION;
-- Code goes here
COMMIT TRANSACTION;
END TRY
BEGIN CATCH
IF ##TRANCOUNT > 0
ROLLBACK TRANSACTION;
DECLARE
#ERROR_SEVERITY INT,
#ERROR_STATE INT,
#ERROR_NUMBER INT,
#ERROR_LINE INT,
#ERROR_MESSAGE NVARCHAR(4000);
SELECT
#ERROR_SEVERITY = ERROR_SEVERITY(),
#ERROR_STATE = ERROR_STATE(),
#ERROR_NUMBER = ERROR_NUMBER(),
#ERROR_LINE = ERROR_LINE(),
#ERROR_MESSAGE = ERROR_MESSAGE();
RAISERROR('Msg %d, Line %d, :%s',
#ERROR_SEVERITY,
#ERROR_STATE,
#ERROR_NUMBER,
#ERROR_LINE,
#ERROR_MESSAGE);
END CATCH
yes it is important to explicitly rollback the transaction in the case that it does not work.
I usually tell my son you only have to brush the teeth you want to keep.
In this case, you only need to rollback the commands you don't want to execute.
This will automatically rollback the transaction in case off error
SET XACT_ABORT ON
BEGIN TRANSACTION
-- CODE HERE
COMMIT TRANSACTION
For transaction control you use begin, commit and rollback. You begin a transaction by supplying BEGIN TRANSACTION. Then you put the various SQL statements you need. Then you end the transaction by issuing either a commit or rollback. COMMIT TRANSACTION will commit all the changes that you did to the database after the BEGIN statement and make them permanent, so to speak. ROLLBACK TRANSACTION will rollback all changes that you did to the database after the BEGIN statement. However, it will not change variable values.
Example:
BEGIN TRANSACTION
UPDATE table SET column = 'ABC' WHERE column = '123'
COMMIT TRANSACTION
--//column now has a value of 'ABC'
BEGIN TRANSACTION
UPDATE table SET column = 'ABC' WHERE column = '123'
ROLLBACK TRANSACTION
--//column still has it's previous value ('123') No changes were made.

Resources