sql >> Base de Datos >  >> RDS >> Sqlserver

Cómo manejar errores en transacciones anidadas de SQL Server

En este artículo, exploraremos las transacciones anidadas de SQL Server, un bloque de transacciones con una o varias transacciones.

La imagen describe un modelo simple de la transacción anidada.

La transacción interna es un procedimiento almacenado que consta de bloques de transacciones. MSDN recomienda "mantener las transacciones lo más cortas posible", lo que es totalmente opuesto al primer enfoque. En mi opinión, no recomiendo usar transacciones anidadas. Aún así, a veces tenemos que usarlos para resolver algunos problemas comerciales.

Por lo tanto, vamos a averiguar:

  • ¿Qué ocurrirá cuando una transacción externa se deshaga o se confirme?
  • ¿Qué ocurrirá cuando una transacción interna se deshaga o se confirme?
  • ¿Cómo manejar los errores de transacciones anidadas?

Para empezar, crearemos una tabla de demostración y probaremos los posibles casos.

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

Caso 1:tanto las transacciones externas como las internas están comprometidas.

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

En este caso, todos los registros se insertan correctamente en la tabla. Asumimos que cada instrucción INSERT no devuelve un error.

Caso 2:la transacción externa se revierte , la transacción interna está comprometida .

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

Como puede ver, los registros no se insertan en la tabla porque la transacción interna es parte de la transacción externa. Por esta razón, la transacción interna retrocede.

Caso 3:la transacción externa está comprometida , la transacción interna se revierte .

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

En este caso, obtuvimos un error e insertamos la última declaración en la tabla. Como resultado, surgen algunas preguntas:

  • ¿Por qué recibimos un error?
  • ¿Por qué se agregó la última instrucción INSERT a la tabla?

Como regla general, la declaración ROLLBACK TRAN revierte todas las transacciones abiertas ejecutadas en la sesión actual. No podemos escribir una consulta porque devolverá un error.

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

Examinaremos cómo esta regla puede afectar nuestro caso. La instrucción ROLLBACK TRAN revierte las transacciones internas y externas. Por esta razón, obtenemos un error al ejecutar la instrucción COMMIT TRAN porque no hay transacciones abiertas.

A continuación, agregaremos una declaración de manejo de errores a esta consulta y la modificaremos según el enfoque de programación defensiva (como dice Wikipedia:la programación defensiva es una forma de diseño defensivo destinada a garantizar la función continua de una pieza de software en circunstancias imprevistas). Cuando escribimos una consulta sin tener cuidado con el manejo de errores y obtenemos un error, podemos enfrentar la corrupción de la integridad de los datos.

Con el siguiente script, usaremos puntos de guardado. Marcan un punto en la transacción y, si lo desea, puede revertir todas las declaraciones DML (lenguaje de manipulación de datos) al punto marcado.

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

Esta consulta manejará el error cuando la transacción interna obtenga un error. Además, las transacciones externas se confirman con éxito. Sin embargo, en algunos casos, si la transacción interna recibe un error, la transacción externa debe revertirse. En este caso, usaremos una variable local que mantendrá y pasará el valor del estado de error de la consulta interna. Diseñaremos la consulta externa con este valor de variable y la consulta será la siguiente.

--<*************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

Conclusiones

En este artículo, exploramos las transacciones anidadas y analizamos cómo manejar los errores en este tipo de consulta. La regla más importante sobre este tipo de transacción es escribir consultas defensivas porque podemos obtener un error en las transacciones externas o internas. Por esta razón, tenemos que diseñar el comportamiento de manejo de errores de la consulta.

Referencias

Transacciones anidadas

GUARDAR TRANSACCIÓN