SAVE TRANSACTION vs BEGIN TRANSACTION (SQL Server) come nidificare correttamente le transazioni
-
14-12-2019 - |
Domanda
Ho una procedura memorizzata che deve impostare un punto di salvataggio in modo che possa, in determinate circostanze, annullare tutto ciò che ha fatto e restituire un codice di errore al chiamante, oppure accettarlo/impegnarlo e restituire l'esito positivo al chiamante.Ma ho bisogno che funzioni indipendentemente dal fatto che il chiamante abbia già avviato una transazione o meno.Il documento è estremamente confuso su questo argomento.Ecco cosa penso che funzionerà, ma non sono sicuro di tutte le ramificazioni.
Il fatto è... questo Stored Procedure (SP)
viene chiamato dagli altri.Quindi non so se hanno avviato una transazione oppure no...Anche se richiedo agli utenti di avviare una transazione per utilizzare il mio SP, ho ancora domande sul corretto utilizzo dello stesso Save Points
...
Il mio SP verificherà se una transazione è in corso e, in caso negativo, ne avvierà una BEGIN TRANSACTION
.Se una transazione è già in corso, verrà invece creato un punto di salvataggio con SAVE TRANSACTION MySavePointName
, e salvo il fatto che questo è quello che ho fatto.
Quindi, se devo ripristinare le modifiche apportate, se ho fatto un file BEGIN TRANSACTION
prima, allora lo farò ROLLBACK TRANSACTION
.Se ho fatto il punto di salvataggio, lo farò ROLLBACK TRANSACTION MySavePointName
.Questo scenario sembra funzionare alla grande.
Qui è dove mi sento un po' confuso: se voglio mantenere il lavoro che ho svolto, se ho avviato una transazione la eseguirò COMMIT TRANSACTION
.Ma se creassi un punto di salvataggio?ho provato COMMIT TRANSACTION MySavePointName
, ma poi il chiamante tenta di confermare la transazione e riceve un errore:
La richiesta COMMIT TRANSACTION non ha BEGIN TRANSACTION corrispondente.
Quindi mi chiedo: è possibile ripristinare un punto di salvataggio (funziona: ROLLBACK TRANSACTION MySavePointName
NON ripristinerà la transazione del chiamante).Ma forse non è mai necessario “impegnarselo”?Rimane lì, nel caso in cui sia necessario ripristinarlo, ma scompare una volta eseguita (o ripristinata) la transazione originale?
Se esiste un modo "migliore" per "nidificare" una transazione, si prega di fare luce anche su questo.Non ho capito come nidificare BEGIN TRANSACTION
ma solo eseguire il rollback o il commit della mia transazione interna.Sembra ROLLBACK
tornerà sempre alla transazione principale, mentre COMMIT
semplicemente diminuisce @@trancount
.
Soluzione
Credo di aver capito tutto ora, quindi risponderò alla mia domanda...
Ho anche pubblicato sul blog le mie scoperte, se desideri maggiori dettagli, all'indirizzo http://geekswithblogs.net/bbiales/archive/2012/03/15/how-to-nest-transactions-nicely---quotbegin-transactionquot-vs-quotsave.aspx
Quindi il mio SP inizia con qualcosa del genere, per iniziare una nuova transazione se non ce n'è, ma usa un punto di salvataggio se ne è già in corso:
DECLARE @startingTranCount int
SET @startingTranCount = @@TRANCOUNT
IF @startingTranCount > 0
SAVE TRANSACTION mySavePointName
ELSE
BEGIN TRANSACTION
-- …
Quindi, quando sei pronto per confermare le modifiche, devi confermare solo se abbiamo avviato noi stessi la transazione:
IF @startingTranCount = 0
COMMIT TRANSACTION
E infine, per ripristinare solo le modifiche apportate finora:
-- Roll back changes...
IF @startingTranCount > 0
ROLLBACK TRANSACTION MySavePointName
ELSE
ROLLBACK TRANSACTION
Altri suggerimenti
Estendendo La risposta di Brian B.
Ciò garantisce che il nome del punto di salvataggio sia univoco e utilizzi le nuove funzionalità TRY/CATCH/THROW di SQL Server 2012.
DECLARE @mark CHAR(32) = replace(newid(), '-', '');
DECLARE @trans INT = @@TRANCOUNT;
IF @trans = 0
BEGIN TRANSACTION @mark;
ELSE
SAVE TRANSACTION @mark;
BEGIN TRY
-- do work here
IF @trans = 0
COMMIT TRANSACTION @mark;
END TRY
BEGIN CATCH
IF xact_state() = 1 OR (@trans = 0 AND xact_state() <> 0) ROLLBACK TRANSACTION @mark;
THROW;
END CATCH
Ho utilizzato questo tipo di gestore delle transazioni nelle mie Stored Procedure:
CREATE PROCEDURE Ardi_Sample_Test
@InputCandidateID INT
AS
DECLARE @TranCounter INT;
SET @TranCounter = @@TRANCOUNT;
IF @TranCounter > 0
SAVE TRANSACTION ProcedureSave;
ELSE
BEGIN TRANSACTION;
BEGIN TRY
/*
<Your Code>
*/
IF @TranCounter = 0
COMMIT TRANSACTION;
END TRY
BEGIN CATCH
IF @TranCounter = 0
ROLLBACK TRANSACTION;
ELSE
IF XACT_STATE() <> -1
ROLLBACK TRANSACTION ProcedureSave;
DECLARE @ErrorMessage NVARCHAR(4000);
DECLARE @ErrorSeverity INT;
DECLARE @ErrorState INT;
SELECT @ErrorMessage = ERROR_MESSAGE();
SELECT @ErrorSeverity = ERROR_SEVERITY();
SELECT @ErrorState = ERROR_STATE();
RAISERROR (@ErrorMessage, @ErrorSeverity, @ErrorState);
END CATCH
GO