how to manage nested transaction with try catch - sql-server

--Drop Table Tab1
Begin Transaction TR1;
Save Transaction TR1;
Create Table Tab1(f1 decimal(10,0));
Begin Transaction TR2
Save Transaction TR2
insert into Tab1 values(1);
Begin Transaction TR3;
Save Transaction TR3;
insert into Tab1 values(2);
Begin Try
insert into Tab1 values('OK');
Commit Transaction TR3;
END TRY
BEGIN Catch
print 'catch'
RollBack Transaction TR3;
End Catch
insert into Tab1 values(3);
Commit Transaction TR2
insert into Tab1 values(4);
Commit Transaction TR1;
--Commit Transaction;
select * from Tab1;
Drop Table Tab1
Select ##TRANCount
Error Occures :
Msg 3931, Level 16, State 1, Line 17
The current transaction cannot be committed and cannot be rolled back to a savepoint. Roll back the entire transaction.
How to handle this.

When certain type of errors get raised you cannot rollback to a save point. See Martin Smith's answer to Rollback transaction to savepoint on failing ALTER TABLE … ADD CONSTRAINT. The way you detect this is to test Xact_state().
However your problem is somewhat different because you're also trying to use nested transactions. Nested transactions don't really work in SQL as we would expect them to.
You can only name the outermost transaction. See Transactions (Database Engine)
For example this fails with Cannot roll back TR2. No transaction or savepoint of that name was found.
BEGIN TRANSACTION TR1;
BEGIN TRANSACTION TR2
ROLLBACK TRANSACTION TR2
COMMIT Transaction TR1
From Nesting Transactions
Committing inner transactions is ignored by the SQL Server Database Engine
It is not legal for the transaction_name parameter of a ROLLBACK TRANSACTION statement to refer to the inner transactions of a set of named nested transactions. transaction_name can refer only to the transaction name of the outermost transaction
Paul S. Randal explores this further in A SQL Server DBA myth a day: (26/30) nested transactions are real
The best you can do is use Save points instead and check the Xact_state in your catch and at the end.
BEGIN TRANSACTION tr1;
SAVE TRANSACTION tr2;
CREATE TABLE tab1
(
f1 DECIMAL(10, 0)
);
SAVE TRANSACTION tr3
INSERT INTO tab1
VALUES (1);
SAVE TRANSACTION tr4;
INSERT INTO tab1
VALUES (2);
BEGIN try
-- change the order of the follwoing two lines around to see the difference
INSERT INTO tab1 VALUES (1 / 0); --Results in a rollback to savepoint
INSERT INTO tab1 VALUES ('OK'); --Results in a complete rollback
COMMIT TRANSACTION tr4;
END try
BEGIN catch
IF Xact_state() = -1
BEGIN
PRINT 'rollback transaction no other work can be done'
ROLLBACK TRANSACTION;
END
ELSE
BEGIN
PRINT 'rollback to savepoint'
ROLLBACK TRANSACTION tr4
END
END catch
IF Xact_state() > 0
BEGIN
INSERT INTO tab1
VALUES (3);
INSERT INTO tab1
VALUES (4);
COMMIT TRANSACTION tr1;
SELECT *
FROM tab1;
DROP TABLE tab1
END

Related

SQL Server Partial commit in transaction

I have a transaction and two tables where i am inserting some data, can I do partial commits in SQL server
BEGIN TRANSACTION tran1
BEGIN TRY
--Insert into Table1
--Insert into Table2
COMMIT TRANSACTION tran1
END TRY
BEGIN CATCH
ROLLBACK TRANSACTION tran1
END CATCH
Above code will rollback both tables data, is there a way we can commit table 1 if there is no error on table 1 insert but rollback table 2 if there is any error occurred.
The answer is yes, although transactions are indeed atomic you could use a savepoint. So in your case, the code could look like this (untested):
BEGIN TRY
BEGIN TRANSACTION
--Insert into Table1
-- savepoint
SAVE TRANSACTION tran1
--Insert into Table2
-- commit the whole transaction
COMMIT TRANSACTION
END TRY
BEGIN CATCH
-- rollback to savepoint
IF ##TRANCOUNT > 0 ROLLBACK TRANSACTION tran1
END CATCH
You may have to adjust insertion order.
Nope, you can't.
A transaction is atomic. That is all of the steps are performed as one, commited together, or rolled back.
If you put both inserts into a single transaction, you can't commit to one table.
You can use different transactions if possible.

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

I have anonymous block
BEGIN
BEGIN TRANSACTION tran1
-- insert query into table
-- insert query into another table
IF 1=2 -- false condition
BEGIN
ROLLBACK TRANSACTION tran1;
END
END
This transactions commits successfully. Then I change IF condition to be true (1=1) and also change transaction name to tran2, so now anonymous block is:
BEGIN
BEGIN TRANSACTION tran2
-- insert query into table
-- insert query into another table
IF 1=1 -- true condition
BEGIN
ROLLBACK TRANSACTION tran2;
END
END
When trying run this block, it returns "query completed with errors" with message:
Cannot roll back tran2. No transaction or savepoint of that name was found.
(also transaction not rollbacks and inserts are performed).
So, why appears that message when there is definitely transaction with name: "tran2" ?

How to prevent a specific INSERT statement from rolling back in a transaction

I have a very huge SP and I have a transaction in it. I am running an algorithm in the SP and if the algorithm doesn't succeed, the transaction gets rolled back.
I need to log some data even if the transaction gets rolled back, but when the transaction is rolled back, it also rolls back the logs as well. This is a normal behavior, but I need to exclude those log insert statements from the the rollback, so the transaction still gets logged.
I have a temp table called #MissingAllocationLines, and I insert my logs into that table. Then if it rollbacks, I need to insert all rows from #MissingAllocationLines into a real table called DLWMS_ALLOCATIONMISSINGLOG
Is that possible? My sample code is below
create table #MissingAllocationLines
(ALLOCATIONJOBID BIGINT,
ORDERID BIGINT,
ORDERDETAILID BIGINT,
ITEMID BIGINT,
STOCKQUANTITY BIGINT,
ORDERQUANTITY BIGINT)
BEGIN TRANSACTION
WHILE(.....)
BEGIN
INSERT INTO #MissingAllocationLines (ALLOCATIONJOBID,ORDERID,ORDERDETAILID,ITEMID,STOCKQUANTITY,ORDERQUANTITY)
VALUES (#ALLOCATIONJOBID,#OrderID,#OrderDetailID,#ItemID,ISNULL(#StockFreeQuantity, 0),ISNULL(#RemainingQuantity,0))
...
...
...
END
IF(#DONE=1)
BEGIN
COMMIT TRANSACTION
END
ELSE
BEGIN
ROLLBACK TRANSCATION
INSERT INTO DLWMS_ALLOCATIONMISSINGLOG (ALLOCATIONJOBID,ORDERID,ORDERDETAILID,ITEMID,STOCKQUANTITY,ORDERQUANTITY)
SELECT ALLOCATIONJOBID,ORDERID,ORDERDETAILID,ITEMID,STOCKQUANTITY,ORDERQUANTITY
FROM #MissingAllocationLines
END
Try using a table variable rather than a temp table. Table variables do not participate in a transaction.
http://zarez.net/?p=1977
http://www.sqlservercentral.com/blogs/steve_jones/2010/09/21/table-variables-and-transactions/
In catch block,before rolling back the transaction,do the following...
DECLARE #TABLE AS TABLE
(COL1 INT,
COL2 INT
...
)
INSERT INTO #TABLE
SELECT * FROM #TEMP TABLE
ROLLBACK TRANSCATION
INSERT INTO DLWMS_ALLOCATIONMISSINGLOG
select * from #table
References:
https://www.itprotoday.com/sql-server/table-variable-tip
You can use 'remote proc transaction promotion' as false and do logging using that linked server to local server: Code as below
begin tran outertran
insert into t values (1)
begin tran innertran
insert into localserver.tempdb.#log values (1)
commit tran innertran
IF (#Done)
begin
commit tran outertran
end
else begin
rollback tran outertran
end
select * from #log
EXEC sp_addlinkedserver #server = N'localserver',#srvproduct = N'',#provider = N'SQLNCLI', #datasrc = ##SERVERNAME
EXEC sp_serveroption localserver,N'remote proc transaction promotion','FALSE'
EXEC sp_serveroption localserver,N'RPC OUT','TRUE' -- Enable RPC to the given server
Check out the SAVE TRANSACTION command.

Transaction count after EXECUTE issue

I have written a procedure like below lines of code
ALTER PROCEDURE [dbo].[CountrySave]
(
#CountryId uniqueidentifier,
#CountryName nvarchar(max)
)
AS
begin tran
if exists (select * from Country where CountryID =#CountryId)
begin
update Country set
CountryID = #CountryId,
CountryName =#CountryName
where CountryID = #CountryId
end
else
begin
insert INTO Country(CountryID, CountryName) values
(NewID(),#CountryName)
end
It throws "Transaction count after EXECUTE indicates a mismatching number of BEGIN and COMMIT statements. Previous count = 0, current count = 1.
A transaction that was started in a MARS batch is still active at the end of the batch. The transaction is rolled back." error message when executed!!!
Please Help...
Add COMMIT TRAN
ALTER PROCEDURE [dbo].[CountrySave]
#CountryId uniqueidentifier,
#CountryName nvarchar(max)
AS
BEGIN
BEGIN TRY
BEGIN TRAN
if exists (select * from Country where CountryID =#CountryId)
begin
update Country
set CountryID = #CountryId,
CountryName =#CountryName
where CountryID = #CountryId;
end
else
begin
insert INTO Country(CountryID, CountryName)
values(NewID(),#CountryName)
end
COMMIT TRAN
END TRY
BEGIN CATCH
/* Error occured log it */
ROLLBACK
END CATCH
END
The error message is fairly clear. When you open (begin) a transaction, you will need to do something at the end of it as well.
So either you ROLLBACK the transaction (in case one of the statements within the transaction fails), or you COMMIT the transaction in order to actually implement all changes your statements made.
From MSDN:
BEGIN TRANSACTION represents a point at which the data referenced by a
connection is logically and physically consistent. If errors are
encountered, all data modifications made after the BEGIN TRANSACTION
can be rolled back to return the data to this known state of
consistency. Each transaction lasts until either it completes without
errors and COMMIT TRANSACTION is issued to make the modifications a
permanent part of the database, or errors are encountered and all
modifications are erased with a ROLLBACK TRANSACTION statement.
More information: https://msdn.microsoft.com/en-us/library/ms188929.aspx
Your Problem is that you begin a transaction but you never commit it / do a rollback.
Try this structure for your procedure, worked very well for me in the past:
CREATE PROCEDURE [dbo].SomeProc
(#Parameter INT)
AS
BEGIN
--if you want to be to only active transaction then uncomment this:
--IF ##TRANCOUNT > 0
--BEGIN
-- RAISERROR('Other Transactions are active at the moment - Please try again later',16,1)
--END
BEGIN TRANSACTION
BEGIN TRY
/*
DO SOMETHING
*/
COMMIT TRANSACTION
END TRY
BEGIN CATCH
--Custom Error could be raised here
--RAISERROR('Something bad happened when doing something',16,1)
ROLLBACK TRANSACTION
END CATCH
END

How to handle nested transaction in stored procedure

I have code like below, now I need to commit only inner transactions and outer transaction may commit or roll-back. How to handle it?
BEGIN TRY
BEGIN TRANSACTION
INSERT TABLE T1 (1,2,3)
----
----
----
IF t1 > 10
BEGIN
BEGIN TRANSACTION
INSERT INTO ERROR_LOG (XX)
COMMIT
return 1
END
COMMIT
END TRY
BEGIN CATCH
ROLLBACK
END CATCH
Only when t1 > 10 then transaction should commit the error log and terminates program
If you're hoping to store some results into ERROR_LOG and rollback other changes, the inner transaction is not going to do that, since in SQL Server the outer transaction will rollback everything.
Here's some testing & explanation done by Paul Randal: http://www.sqlskills.com/blogs/paul/a-sql-server-dba-myth-a-day-2630-nested-transactions-are-real/
One way to get around of this limitation is to use table variable for the logging, since it will not be rolled back, and then insert the results into the log after doing the rollback.

Resources