Rollback an entire stored procedure - sql

I have a stored procedure with multiple update statements.I dont want to use try catch.How can I rollback the stored procedure and get back the original table?
can something like this work -
begin transaction t1
spName
rollback transaction t1

Yes you can wrap everything into a sproc into a transaction
begin tran
exec testproc
commit tran
--rollback tran --for condition
It works fine even for commit as well rollback
If for inside the sproc you need to open another transaction then you need to capture
DECLARE #vTranCount INT = ##TRANCOUNT
--Commit
IF (#vTranCount = 0 AND ##TRANCOUNT <> 0) COMMIT TRANSACTION --Commit if the Tran is created by this sproc
--rollback during catch
IF(#vTranCount = 0 AND ##TRANCOUNT > 0) ROLLBACK TRANSACTION --Rollback if the Tran is created by this sproc

Related

How to handle Transaction in Nested procedure in SQL server?

I have 2 proc i.e. Proc1 and Proc2.
I am executing proc1 inside proc2. There are multiple DML operation in both procedure. output of proc1 is used in proc2 for DML operation.
if Error occurred in proc2 then
how to handle transaction in both proc for rollback all DML operation?
Should I write transaction in both proc?
We use a generic error handler procedure based on http://www.sommarskog.se/error_handling/Part1.html that we - when applicable - include in our (nested) transactions to ensure the chain is managed properly:
CREATE PROCEDURE [dbo].[sp_ErrorHandler](#caller VARCHAR(255))
AS BEGIN
SET NOCOUNT ON;
DECLARE #errmsg NVARCHAR(2048), #severity TINYINT, #state TINYINT, #errno INT, #lineno INT;
SELECT #errmsg=REPLACE(ERROR_MESSAGE(), 'DatabaseException: ', 'DatabaseException: '+QUOTENAME(#caller)+' --> ')
, #severity=ERROR_SEVERITY()
, #state=ERROR_STATE()
, #errno=ERROR_NUMBER()
, #lineno=ERROR_LINE();
IF #errmsg NOT LIKE 'DatabaseException%' BEGIN
SELECT #errmsg=N'DatabaseException: '+QUOTENAME(#caller)+N', Line '+LTRIM(STR(#lineno))+N', Error '+LTRIM(STR(#errno))+N': '+#errmsg;
END;
RAISERROR('%s', #severity, #state, #errmsg);
END;
(Compiled in the master database and marked as system procedure)
We use this error handler as follows. In the demo I have an outer proc and an inner proc both using a transaction.
CREATE PROCEDURE dbo.uspOuterProc
AS
BEGIN
SET NOCOUNT, XACT_ABORT ON;
BEGIN TRY
BEGIN TRANSACTION;
EXEC dbo.uspInnerProc;
PRINT 1;
COMMIT;
END TRY
BEGIN CATCH
IF ##trancount > 0
ROLLBACK TRANSACTION;
EXEC master.dbo.sp_ErrorHandler #caller = 'dbo.uspOuterProc';
END CATCH;
END;
GO
CREATE PROCEDURE dbo.uspInnerProc
AS
BEGIN
SET NOCOUNT, XACT_ABORT ON;
BEGIN TRY
BEGIN TRANSACTION;
PRINT 2;
SELECT 1 / 0;
PRINT 3;
COMMIT;
END TRY
BEGIN CATCH
IF ##trancount > 0
ROLLBACK TRANSACTION;
EXEC master.dbo.sp_ErrorHandler #caller = 'dbo.uspInnerProc';
END CATCH;
END;
GO
After you compile this and run:
EXEC dbo.uspOuterProc
You should get this result:
2
Msg 50000, Level 16, State 1, Procedure sp_ErrorHandler, Line 13 [Batch Start Line 48]
DatabaseException: [dbo.uspOuterProc] --> [dbo.uspInnerProc], Line 12, Error 8134: Divide by zero error encountered.
You can handle the transaction in the outer procedure ( in your case proc2). If any error will be occurred in proc1 it will be taken care of by proc2 transaction handler.
Am assuming that proc1 will not be called directly, it will be called inside the proc2.
There are 3 basic transaction handling statements (and a few advanced ones I'm not gonna mention):
BEGIN TRANSACTION: Will raise the ##TRANCOUNT session variable by 1. If it goes from 0 to 1 then this marks the start of a transaction. Any value higher than 1 will keep the same transaction ongoing.
COMMIT: Will lower the ##TRANCOUNT session variable by 1. If it goes from 1 to 0 then the transaction is marked as finished and will impact all changes done since it was first created.
ROLLBACK: Will decrease the ##TRANCOUNT session variable to 0 (whichever it's value was), as long as it was at least 1 or higher. This will close the transaction and revert all changes done since it was first created.
Nested transactions are a bunch of BEGIN TRANSACTION statements put together. The only point where the transaction gets fully commited and the changes are made permanent is when there is a COMMIT that lowers the transaction count from 1 to 0. That means you need one COMMIT for each BEGIN TRANSACTION you executed, like a pyramid.
Check the following example:
BEGIN TRANSACTION
SELECT ##TRANCOUNT -- 1
BEGIN TRANSACTION
SELECT ##TRANCOUNT -- 2
COMMIT TRANSACTION
SELECT ##TRANCOUNT -- 1 (no change is permanent yet, not even the last one)
BEGIN TRANSACTION
SELECT ##TRANCOUNT -- 2
ROLLBACK
SELECT ##TRANCOUNT -- 0 (all changes were discarded)
When you have an SP that executes another SP and both have their transactions, the only thing you need to care about is to CATCH errors and do the proper ROLLBACK IF there's an open/active transaction ongoing (if not the ROLLBACK statement will fail saying that there is nothing to rollback).
A very basic CATCH would be like the following:
BEGIN TRY
BEGIN TRANSACTION
/* Do some operations */
/* Execute another SP that might have the following:
BEGIN TRANSACTION
-- Some other operations
COMMIT
*/
COMMIT
END TRY
BEGIN CATCH
DECLARE #v_ErrorMessage VARCHAR(MAX) = ERROR_MESSAGE()
IF ##TRANCOUNT > 0 -- Only rollback if there is an active transaction
ROLLBACK
RAISERROR (#v_ErrorMessage, 16, 1)
END CATCH
You can read this post if you want to delve deeply into the best way for handling transactions on SQL Server.

Can I open 2 transactions with different names

Can I have 2 transactions in a stored procedure like below? If I rollback TRAN1, does it rollback all TRAN2? I am lost here, any feedback will help. Thanks in advance
BEGIN TRANSACTION TRAN1
WHILE(...)
BEGIN
BEGIN TRANSACTION TRAN2
BEGIN TRY
.....
.....
.....
COMMIT TRANSACTION TRAN2
END TRY
BEGIN CATCH
ROLLBACK TRANSACTION TRAN2
END CATCH
END
COMMIT TRANSACTION TRAN1
No matter ,how many transactions you have ,Outer rollback rollbacks all the transactions..
When you nest transactions like in your case,each committ/rollback increases or decreases ##trancount..
From MSDN..
Each BEGIN TRANSACTION statement increments ##TRANCOUNT by one. Each COMMIT TRANSACTION or COMMIT WORK statement decrements ##TRANCOUNT by one.
ROLLBACK TRANSACTION that uses the transaction name of the outermost transaction in a set of nested transactions rolls back all of the nested transactions and decrements ##TRANCOUNT to 0
See this as well :A SQL Server DBA myth a day: (26/30) nested transactions are real
Below is demo to test this behaviour..
if object_id('t1','u') is not null
drop table t1
create table t1
(
id int
)
go
begin tran outertran
select ##trancount--1
insert into t1
select 1
begin tran innertran
select ##trancount--2
insert into t1
select 2
/**below throws error,since you can reference only outer transaction
-referrring inner tran is not legal
***/
--rollback tran innertran
/***
error you get by uncommneting above
Msg 6401, Level 16, State 1, Line 20
Cannot roll back innertran. No transaction or savepoint of that name was found.
**/
commit tran innertran
select ##trancount--1
rollback --rollbacks all
select * from t1
References :
http://www.sqlskills.com/blogs/paul/a-sql-server-dba-myth-a-day-2630-nested-transactions-are-real/

Sql Server nested transaction rollback ##TRANCOUNT

I've got a stored procedure which begins a new transaction for data manipulation. The procedure itself is executed within another transaction.
I have no influence what happens before my procedure. And it could change.
My idea is to check the ##TRANCOUNT before I begin the nested transaction. Then check the ##TRANCOUNT again in catch block and compare it. In no case I want the outer transaction to be rollbacked. So i wonder if i am safe with this code?
thx for your help!
SET #TRANSCOUNTBEFORE = ##TRANCOUNT;
BEGIN TRANSACTION tx;
BEGIN TRY
/* some data manipulation here */
COMMIT TRANSACTION tx;
END TRY
BEGIN CATCH
IF ##TRANCOUNT > #TRANSCOUNTBEFORE ROLLBACK TRANSACTION tx;
/* some error handling here */
END CATCH;

There are uncommitted transactions

SET XACT_ABORT ON
BEGIN TRY
BEGIN TRAN
INSERT INTO dbo.Student
(FirstName, LastName)
VALUES
('Jon','Ye')
IF XACT_STATE() = 0
COMMIT TRAN
END TRY
BEGIN CATCH
IF XACT_STATE() <> 1
ROLLBACK TRAN
ELSE
COMMIT TRANSACTION
END CATCH
RETURN
GO
Error Message:
There are uncommitted transactionS.
I only see results when I close SQL Server.
the problem is here
IF XACT_STATE() = 0
COMMIT TRAN
XACT_STATE() only returns 0 if no active user transaction exists. Due to the open transaction started by BEGIN TRAN, XACT_STATE must be returning 1 and the COMMIT TRAN consequently does not execute. You should be checking for a return value of 1

Multiple transactions in a single stored procedure

I need to delete data from 2 tables in SQL Server 2008.
I have two tables A and B. I need to put each in a separate transaction.
This is the code I am using.
BEGIN TRANSACTION;
BEGIN TRY
DELETE from A
END TRY
BEGIN CATCH
IF ##TRANCOUNT > 0
ROLLBACK TRANSACTION;
END CATCH
IF ##TRANCOUNT > 0
COMMIT TRANSACTION;
BEGIN TRANSACTION;
BEGIN TRY
DELETE from B
END TRY
BEGIN CATCH
IF ##TRANCOUNT > 0
ROLLBACK TRANSACTION;
END CATCH
IF ##TRANCOUNT > 0
COMMIT TRANSACTION;
Is there any better way to implement multiple transactions and error handling in SQL Server?
I need to put separate transactions for each table.
I am getting an error when one of the transactions is failed.
Transaction count after EXECUTE indicates a mismatching number of
BEGIN and COMMIT statements. the records are not deleting from other
transaction
try this:
BEGIN TRANSACTION;
BEGIN TRY
DELETE from A
COMMIT TRANSACTION;
END TRY
BEGIN CATCH
ROLLBACK TRANSACTION;
END CATCH
BEGIN TRANSACTION;
BEGIN TRY
DELETE from B
COMMIT TRANSACTION;
END TRY
BEGIN CATCH
ROLLBACK TRANSACTION;
END CATCH
Every query is in an implicit transaction, so an explicit transaction for a single query is redundant. A DELETE will either delete all the records or none of them, not some of them and then fail.
If the two delete queries need to succeed or fail together, then the explicit transaction is required.
If these are huge tables, then deleting a few records at a time can be more efficient without an explicit transaction. (Done this many times.) However, if it has to be a single transaction, then putting the separate smaller deletes in a larger transaction will not prevent transaction log bloat. I don't know if it will be faster in this case. In the old days, I remember using a single transaction could require hours rather than minutes to complete the process. In one case, it would never finish...we gave up after several days.
May be you can do this way:
BEGIN TRANSACTION one;
BEGIN TRY
DELETE from A
END TRY
BEGIN CATCH
IF ##TRANCOUNT > 0
ROLLBACK TRANSACTION one;
END CATCH
IF ##TRANCOUNT > 0
COMMIT TRANSACTION one;
BEGIN TRANSACTION two;
BEGIN TRY
DELETE from B
END TRY
BEGIN CATCH
IF ##TRANCOUNT > 0
ROLLBACK TRANSACTION two;
END CATCH
IF ##TRANCOUNT > 0
COMMIT TRANSACTION two;