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