Sqlserver
 sql >> Base de Dados >  >> RDS >> Sqlserver

Como lidar com erros em transações aninhadas do SQL Server


Neste artigo, exploraremos as transações aninhadas do SQL Server, um bloco de transação com uma ou várias transações.

A imagem descreve um modelo simples da transação aninhada.





A transação interna é um procedimento armazenado que consiste em blocos de transação. O MSDN recomenda “manter as transações o mais curtas possível” que é totalmente oposta à primeira abordagem. Na minha opinião, não recomendo usar transações aninhadas. Ainda assim, às vezes temos que usá-los para resolver alguns problemas de negócios.

Assim, vamos descobrir:
  • O que ocorrerá quando uma transação externa for revertida ou confirmada?
  • O que ocorrerá quando uma transação interna for revertida ou confirmada?
  • Como lidar com erros de transações aninhadas?

Para começar, vamos criar uma tabela de demonstração e testar possíveis casos.
USE AdventureWorks
-----Create Demo Table----
CREATE TABLE CodingSightDemo
(NumberValue VARCHAR(20))

Caso 1:as transações externas e internas são confirmadas.
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



Neste caso, todos os registros são inseridos com sucesso na tabela. Assumimos que toda instrução INSERT não retorna um erro.

Caso 2:a transação externa é revertida , a transação interna é confirmada .
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 você pode ver, os registros não são inseridos na tabela porque a transação interna faz parte da transação externa. Por esse motivo, a transação interna é revertida.

Caso 3:a transação externa é confirmada , a transação interna é revertida .
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





Nesse caso, recebemos um erro e inserimos a última instrução na tabela. Como resultado, surgem algumas dúvidas:
  • Por que recebemos um erro?
  • Por que a instrução INSERT mais recente foi adicionada à tabela?

Como regra, a instrução ROLLBACK TRAN reverte todas as transações abertas executadas na sessão atual. Não podemos escrever uma consulta porque ela retornará um erro.
BEGIN TRAN
INSERT INTO CodingSightDemo	
VALUES('One')	
BEGIN TRAN
INSERT INTO CodingSightDemo	
VALUES('Two')	
ROLLBACK TRAN
ROLLBACK TRAN



Examinaremos como essa regra pode impactar nosso caso. A instrução ROLLBACK TRAN reverte transações internas e externas. Por esse motivo, obtemos um erro ao executar a instrução COMMIT TRAN porque não há transações abertas.



Em seguida, adicionaremos uma instrução de tratamento de erros a essa consulta e a modificaremos com base na abordagem de programação defensiva (como afirma a Wikipedia:A programação defensiva é uma forma de design defensivo destinada a garantir a função contínua de um software sob circunstâncias imprevistas). Quando escrevemos uma consulta sem cuidar do tratamento de erros e obtemos um erro, podemos enfrentar a corrupção da integridade dos dados.

Com o próximo script, usaremos pontos de salvamento. Eles marcam um ponto na transação e, se você quiser, pode reverter todas as instruções DML (Data Manipulation Language) para o ponto 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 tratará o erro quando a transação interna obtiver um erro. Além disso, as transações externas são confirmadas com êxito. No entanto, em alguns casos, se a transação interna receber um erro, a transação externa deverá ser revertida. Nesse caso, usaremos uma variável local que manterá e passará o valor do estado de erro da consulta interna. Vamos projetar a consulta externa com esse valor de variável e a consulta será a seguinte.
--<*************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

Conclusões


Neste artigo, exploramos as transações aninhadas e analisamos como lidar com erros nesse tipo de consulta. A regra mais importante sobre esse tipo de transação é escrever consultas defensivas porque podemos obter um erro em transações externas ou internas. Por esse motivo, precisamos projetar o comportamento de tratamento de erros da consulta.

Referências


Aninhamento de transações

SALVAR TRANSAÇÃO