Written by 16:41 Database administration, Troubleshooting Issues

How to Handle Errors in SQL Server Nested Transactions

In this article, we will explore SQL Server Nested Transactions, a transaction block with one or several transactions.

The image describes a simple model of the nested transaction.

The inner transaction is a stored procedure that consists of transaction blocks. MSDN recommends “keeping transactions as short as possible” that is totally opposite to the first approach. In my opinion, I do not recommend using nested transactions. Still, sometimes we have to use them to solve some business problems.

Thus, we are going to figure out:

  • What will occur when an outer transaction is rolled back or committed?
  • What will occur when an inner transaction is rolled back or committed?
  • How to handle nested transactions errors?

To begin with, we will create a demo table and test possible cases.

USE AdventureWorks
-----Create Demo Table----
CREATE TABLE CodingSightDemo
(NumberValue VARCHAR(20))

Case 1: Both outer and inner transactions are committed.

TRUNCATE TABLE CodingSightDemo  
--<*************OUTHER TRANSACTION START*************>
BEGIN TRAN				   
INSERT INTO CodingSightDemo	
VALUES('One')				
--<INNER TRANSACTION START>
BEGIN TRAN 					
INSERT INTO CodingSightDemo 		
VALUES('Two') 			
COMMIT TRAN	 			
--< INNER TRANSACTION END>
INSERT INTO CodingSightDemo 								VALUES('Three')				  
COMMIT TRAN		
--<************* OUTHER TRANSACTION END*************>
SELECT * FROM CodingSightDemo

In this case, all the records are successfully inserted into the table. We assumed that every INSERT statement does not return an error.

Case 2: Outer transaction is rolled back, inner transaction is committed.

TRUNCATE TABLE CodingSightDemo  
--<*************OUTHER TRANSACTION START*************>
BEGIN TRAN				   
INSERT INTO CodingSightDemo	
VALUES('One')				
--<INNER TRANSACTION START>
BEGIN TRAN 					
INSERT INTO CodingSightDemo 		
VALUES('Two') 			
COMMIT TRAN	 			
--< INNER TRANSACTION END>
INSERT INTO CodingSightDemo VALUES('Three')				  
rollback TRAN		
--<************* OUTHER TRANSACTION END*************>
SELECT * FROM CodingSightDemo

As you can see, the records aren’t inserted into the table because the inner transaction is a part of the outer transaction. For this reason, the inner transaction rolls back.

Case 3: Outer transaction is committed, inner transaction is rolled back.

TRUNCATE TABLE CodingSightDemo  
--<*************OUTHER TRANSACTION START*************>
BEGIN TRAN				   
INSERT INTO CodingSightDemo	
VALUES('One')				
--<INNER TRANSACTION START>
BEGIN TRAN 					
INSERT INTO CodingSightDemo 		
VALUES('Two') 			
ROLLBACK TRAN	 			
--< INNER TRANSACTION END>
INSERT INTO CodingSightDemo VALUES('Three')				  
COMMIT TRAN		
--<************* OUTHER TRANSACTION END*************>
SELECT * FROM CodingSightDemo

In this case, we got an error and inserted the latest statement into the table. As a result, some questions arise:

  • Why did we get an error?
  • Why was the latest INSERT statement added to the table?

As a rule, the ROLLBACK TRAN statement rolls back all open transactions executed in the current session. We cannot write a query because it will return an error.

BEGIN TRAN
INSERT INTO CodingSightDemo	
VALUES('One')	
BEGIN TRAN
INSERT INTO CodingSightDemo	
VALUES('Two')	
ROLLBACK TRAN
ROLLBACK TRAN

We will examine how this rule may impact our case. The ROLLBACK TRAN statement rolls back inner and outer transactions. For this reason, we get an error when running the COMMIT TRAN statement because there are no open transactions.

Next, we will add an error handling statement to this query and modify it based on the defensive programming approach (as Wikipedia states: Defensive programming is a form of defensive design intended to ensure the continuing function of a piece of software under unforeseen circumstances). When we write a query without taking care about error handling and get an error, we may face the data integrity corruption.

With the next script, we will use save points. They mark a point in the transaction and if you want, you can rollback all DML (Data Manipulation Language) statements to the marked point.

BEGIN TRY
BEGIN TRAN				   
INSERT INTO CodingSightDemo	
VALUES('One')				
--<INNER TRANSACTION START>
SAVE TRANSACTION innerTRAN
BEGIN TRY
BEGIN TRAN 					
INSERT INTO CodingSightDemo 		
VALUES('Two') 			
COMMIT TRAN
END TRY		
BEGIN CATCH
IF XACT_STATE() <> 0
BEGIN 
ROLLBACK TRANSACTION innerTRAN
PRINT 'Roll back occurs for inner tran'
END
IF XACT_STATE() <> 0
BEGIN 
COMMIT TRAN 
PRINT 'Commit occurs for firt open tran'
END
END CATCH
--< INNER TRANSACTION END>
INSERT INTO CodingSightDemo VALUES('Three')				  
COMMIT TRAN		
END TRY
BEGIN CATCH
BEGIN
IF XACT_STATE() <> 0
ROLLBACK TRAN 
PRINT 'Roll back occurs for outer tran'
END
END CATCH
--<************* OUTHER TRANSACTION END*************>
SELECT * FROM CodingSightDemo

This query will handle the error when the inner transaction gets an error. Also, outer transactions are successfully committed. However, in some cases, if the inner transaction gets an error, the outer transaction must roll back. In this case, we will use a local variable that will keep and pass the inner query error state value. We will design the outer query with this variable value and the query will be as follows.

--<*************OUTHER TRANSACTION START*************>
DECLARE @innertranerror as int=0
BEGIN TRY
BEGIN TRAN				   
INSERT INTO CodingSightDemo	
VALUES('One')				
--<INNER TRANSACTION START>
SAVE TRANSACTION innerTRAN
BEGIN TRY
BEGIN TRAN 					
INSERT INTO CodingSightDemo 		
VALUES('Two') 			
COMMIT TRAN
END TRY		
BEGIN CATCH
IF XACT_STATE() <> 0
BEGIN 
SET @innertranerror=1
ROLLBACK TRANSACTION innerTRAN
PRINT 'Roll back occurs for inner tran'
END
IF XACT_STATE() <> 0
BEGIN 
COMMIT TRAN 
PRINT 'Commit occurs for firt open tran'
END
END CATCH
--< INNER TRANSACTION END>
INSERT INTO CodingSightDemo VALUES('Three')	
if @innertranerror=0
BEGIN
COMMIT TRAN	
END
IF @innertranerror=1
BEGIN
ROLLBACK TRAN
END

END TRY
BEGIN CATCH
BEGIN
IF XACT_STATE() <> 0
ROLLBACK TRAN 
PRINT 'Roll back occurs for outer tran'
END
END CATCH
--<************* OUTHER TRANSACTION END*************>
SELECT * FROM CodingSightDemo

Conclusions

In this article, we explored nested transactions and analyzed how to handle errors in this type of query. The most important rule about this transaction type is to write defensive queries because we can get an error in outer or inner transactions. For this reason, we have to design error handling behavior of the query. 

References

Nesting Transactions

SAVE TRANSACTION

Tags: , , Last modified: September 22, 2021
Close