Sqlserver
 sql >> Database >  >> RDS >> Sqlserver

Come gestire gli errori nelle transazioni annidate di SQL Server

In questo articolo esploreremo le transazioni nidificate di SQL Server, un blocco di transazione con una o più transazioni.

L'immagine descrive un semplice modello della transazione annidata.

La transazione interna è una procedura memorizzata costituita da blocchi di transazione. MSDN consiglia di "mantenere le transazioni il più brevi possibile" che è totalmente opposto al primo approccio. A mio parere, non consiglio di utilizzare le transazioni nidificate. Tuttavia, a volte dobbiamo usarli per risolvere alcuni problemi aziendali.

Quindi, scopriremo:

  • Cosa accadrà quando viene eseguito il rollback o il commit di una transazione esterna?
  • Cosa accadrà quando viene eseguito il rollback o il commit di una transazione interna?
  • Come gestire gli errori delle transazioni nidificate?

Per cominciare, creeremo una tabella demo e testeremo possibili casi.

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

Caso 1:le transazioni esterne e interne sono impegnate.

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 questo caso, tutti i record vengono inseriti correttamente nella tabella. Abbiamo presupposto che ogni istruzione INSERT non restituisca un errore.

Caso 2:la transazione esterna è rollback , la transazione interna è impegnata .

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

Come puoi vedere, i record non vengono inseriti nella tabella perché la transazione interna fa parte della transazione esterna. Per questo motivo, la transazione interna esegue il rollback.

Caso 3:la transazione esterna è impegnata , la transazione interna viene rollback .

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 questo caso, abbiamo ricevuto un errore e abbiamo inserito l'ultima istruzione nella tabella. Di conseguenza, sorgono alcune domande:

  • Perché abbiamo ricevuto un errore?
  • Perché l'ultima istruzione INSERT è stata aggiunta alla tabella?

Di norma, l'istruzione ROLLBACK TRAN esegue il rollback di tutte le transazioni aperte eseguite nella sessione corrente. Non possiamo scrivere una query perché restituirà un errore.

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

Esamineremo come questa regola può influire sul nostro caso. L'istruzione ROLLBACK TRAN esegue il rollback delle transazioni interne ed esterne. Per questo motivo, viene visualizzato un errore durante l'esecuzione dell'istruzione COMMIT TRAN perché non ci sono transazioni aperte.

Successivamente, aggiungeremo un'istruzione di gestione degli errori a questa query e la modificheremo in base all'approccio di programmazione difensivo (come afferma Wikipedia:la programmazione difensiva è una forma di progettazione difensiva destinata a garantire il funzionamento continuo di un pezzo di software in circostanze impreviste). Quando scriviamo una query senza occuparci della gestione degli errori e riceviamo un errore, potremmo dover affrontare il danneggiamento dell'integrità dei dati.

Con il prossimo script, utilizzeremo i punti di salvataggio. Contrassegnano un punto nella transazione e, se lo desideri, puoi ripristinare tutte le istruzioni DML (Data Manipulation Language) al punto contrassegnato.

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

Questa query gestirà l'errore quando la transazione interna riceve un errore. Inoltre, le transazioni esterne vengono salvate correttamente. Tuttavia, in alcuni casi, se la transazione interna riceve un errore, è necessario eseguire il rollback della transazione esterna. In questo caso, utilizzeremo una variabile locale che manterrà e passerà il valore dello stato di errore della query interna. Progetteremo la query esterna con questo valore variabile e la query sarà la seguente.

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

Conclusioni

In questo articolo abbiamo esplorato le transazioni nidificate e analizzato come gestire gli errori in questo tipo di query. La regola più importante su questo tipo di transazione è scrivere query difensive perché possiamo ottenere un errore nelle transazioni esterne o interne. Per questo motivo, dobbiamo progettare il comportamento di gestione degli errori della query.

Riferimenti

Operazioni di annidamento

SALVA TRANSAZIONE