Come eseguire il rollback quando vengono avviate 3 stored procedure da una stored procedure


23

Ho una procedura memorizzata che esegue solo 3 stored procedure al loro interno. Sto solo usando 1 parametro per memorizzare se l'SP principale ha successo.

Se la prima procedura memorizzata funziona correttamente nella procedura memorizzata principale, ma la seconda procedura memorizzata ha esito negativo, eseguirà automaticamente il rollback di tutti gli SP nell'SP principale o devo eseguire un comando?

Ecco la mia procedura:

CREATE PROCEDURE [dbo].[spSavesomename] 
    -- Add the parameters for the stored procedure here

    @successful bit = null output
AS
BEGIN
begin transaction createSavebillinginvoice
    begin Try
    -- SET NOCOUNT ON added to prevent extra result sets from
    -- interfering with SELECT statements.
    SET NOCOUNT ON;

   BEGIN 

   EXEC [dbo].[spNewBilling1]

   END

   BEGIN 

   EXEC [dbo].[spNewBilling2]

   END

   BEGIN 

   EXEC [dbo].[spNewBilling3]

   END 

   set @successful  = 1

   end Try

    begin Catch
        rollback transaction createSavesomename
        insert into dbo.tblErrorMessage(spName, errorMessage, systemDate) 
             values ('spSavesomename', ERROR_MESSAGE(), getdate())

        return
    end Catch
commit transaction createSavesomename
return
END

GO

Se spNewBilling3genera un errore, ma non si desidera eseguire il rollback spNewBilling2o spNewBilling1, quindi rimuoverlo [begin|rollback|commit] transaction createSavebillinginvoiceda spSavesomename.
Mike,

Risposte:


56

Dato solo il codice mostrato nella domanda e supponendo che nessuno dei tre sottoprocessi abbia una gestione esplicita delle transazioni, allora sì, verrà rilevato un errore in uno dei tre sottoprocessi e ROLLBACKnel CATCHblocco verrà eseguito il rollback di tutti del lavoro.

MA qui ci sono alcune cose da notare sulle transazioni (almeno in SQL Server):

  • Esiste sempre una sola transazione reale (la prima), indipendentemente da quante volte chiamiBEGIN TRAN

    • Puoi nominare una transazione (come hai fatto qui) e quel nome apparirà nei registri, ma la denominazione ha significato solo per la prima / più esterna transazione (perché, di nuovo, la prima è la transazione).
    • Ogni volta che hai chiamato BEGIN TRAN, indipendentemente dal nome, il contatore delle transazioni viene incrementato di 1.
    • Puoi vedere il livello attuale facendo SELECT @@TRANCOUNT;
    • Qualsiasi COMMITcomando emesso quando @@TRANCOUNTè 2 o superiore non fa altro che ridurre, uno alla volta, il contatore delle transazioni.
    • Nulla viene mai commesso fino a quando non COMMITviene emesso un a quando @@TRANCOUNTè a1
    • Nel caso in cui le informazioni di cui sopra non indicano chiaramente: indipendentemente dal livello di transazione, non vi è alcuna nidificazione effettiva delle transazioni.
  • I punti di salvataggio consentono di creare un sottoinsieme di lavoro all'interno della transazione che può essere annullato.

    • I punti di salvataggio vengono creati / contrassegnati tramite il SAVE TRAN {save_point_name}comando
    • I punti di salvataggio segnano l' inizio del sottoinsieme di lavoro che può essere annullato senza ripristinare l'intera transazione.
    • I nomi dei punti di salvataggio non devono essere univoci, ma l'utilizzo dello stesso nome più di una volta crea ancora punti di salvataggio distinti.
    • I punti di salvataggio possono essere nidificati.
    • I punti di salvataggio non possono essere impegnati.
    • I punti di salvataggio possono essere annullati tramite ROLLBACK {save_point_name}. (ulteriori informazioni qui sotto)
    • Il rollback di un punto di salvataggio annullerà qualsiasi lavoro avvenuto dopo l' ultima chiamata SAVE TRAN {save_point_name}, inclusi eventuali punti di salvataggio creati dopo la creazione di quello da ripristinare (da cui il "annidamento").
    • Il rollback di un punto di salvataggio non ha effetto sul conteggio / livello della transazione
    • Qualsiasi lavoro svolto prima dell'iniziale SAVE TRANnon può essere annullato se non emettendo un intero ROLLBACKdell'intera transazione.
    • Giusto per essere chiari: l'emissione di un COMMITquando @@TRANCOUNTè pari a 2 o superiore, non ha alcun effetto sui punti di salvataggio (perché, di nuovo, i livelli di transazione superiori a 1 non esistono al di fuori di quel contatore).
  • Non è possibile eseguire il commit di specifiche transazioni denominate. Il "nome" della transazione, se fornito insieme a COMMIT, viene ignorato ed esiste solo per leggibilità.

  • Un ROLLBACKemesso senza nome sarà sempre far ritirare tutte le transazioni.

  • Un ROLLBACKrilasciato con un nome deve corrispondere a uno:

    • La prima transazione, supponendo che sia stata denominata:
      Supponendo che non SAVE TRANsia stata chiamata con lo stesso nome di transazione, verranno ripristinate TUTTE le transazioni.
    • Un "punto di salvataggio" (descritto sopra):
      questo comportamento "annulla" tutte le modifiche apportate dalla chiamata più recente SAVE TRAN {save_point_name} .
    • Se la prima transazione è stata denominata a) eb) ha ricevuto SAVE TRANcomandi con il suo nome, ogni ROLLBACK con quel nome di transazione annullerà ogni punto di salvataggio fino a quando non ne rimarrà più nessuno. Successivamente, un ROLLBACK emesso con quel nome eseguirà il rollback di TUTTE le transazioni.
    • Ad esempio, supponiamo che i seguenti comandi siano stati eseguiti nell'ordine mostrato:

      BEGIN TRAN A -- @@TRANCOUNT is now 1
      -- DML Query 1
      SAVE TRAN A
      -- DML Query 2
      SAVE TRAN A
      -- DML Query 3
      
      BEGIN TRAN B -- @@TRANCOUNT is now 2
      SAVE TRAN B
      -- DML Query 4

      Ora, se si emette (ciascuno dei seguenti scenari è indipendente l'uno dall'altro):

      • ROLLBACK TRAN Buna volta: annullerà "DML Query 4". @@TRANCOUNTè ancora 2.
      • ROLLBACK TRAN Bdue volte: annullerà "DML Query 4" e quindi si verificherà un errore poiché non esiste un punto di salvataggio corrispondente per "B". @@TRANCOUNTè ancora 2.
      • ROLLBACK TRAN Auna volta: annullerà "DML Query 4" e "DML Query 3". @@TRANCOUNTè ancora 2.
      • ROLLBACK TRAN Adue volte: annullerà "DML Query 4", "DML Query 3" e "DML Query 2". @@TRANCOUNTè ancora 2.
      • ROLLBACK TRAN Atre volte: annullerà "DML Query 4", "DML Query 3" e "DML Query 2". Quindi eseguirà il rollback dell'intera transazione (è rimasto solo "DML Query 1"). @@TRANCOUNTè ora 0.
      • COMMITuna volta: @@TRANCOUNTscende a 1.
      • COMMITuna volta e poi ROLLBACK TRAN Buna volta: @@TRANCOUNTscende a 1. Quindi annulla "DML Query 4" (dimostrando che COMMIT non ha fatto nulla). @@TRANCOUNTè ancora 1.
  • Nomi delle transazioni e nomi dei punti di salvataggio:

    • può avere un massimo di 32 caratteri
    • vengono considerati come regole di confronto binarie (senza distinzione tra maiuscole e minuscole come indicato attualmente nella documentazione), indipendentemente dalle regole di confronto a livello di istanza o a livello di database.
    • Per i dettagli, vedere la sezione Nomi delle transazioni del seguente post: Cosa c'è in un nome ?: Nel mondo stravagante degli identificatori T-SQL
  • Una procedura memorizzata non è di per sé una transazione implicita. Ogni query, se non è stata avviata alcuna transazione esplicita, è una transazione implicita. Questo è il motivo per cui non sono necessarie transazioni esplicite intorno a singole query a meno che non ci sia un motivo programmatico per fare un ROLLBACK, altrimenti qualsiasi errore nella query è un rollback automatico di quella query.

  • Quando si chiama una procedura memorizzata, deve uscire con il valore di @@TRANCOUNTessere la stessa di quando è stata chiamata. Significato, non puoi:

    • Avvia a BEGIN TRANnel proc senza impegnarlo, aspettandoti di impegnarti nel processo chiamante / genitore.
    • Non è possibile emettere un ROLLBACKse è stata avviata una transazione esplicita prima che il proc fosse chiamato poiché tornerà @@TRANCOUNTa 0.

    Se si esce da una stored procedure con un conteggio delle transazioni superiore o inferiore rispetto a quando è stato avviato, verrà visualizzato un errore simile a:

    Messaggio 266, livello 16, stato 2, procedura YourProcName, riga 0 Il
    conteggio delle transazioni dopo EXECUTE indica un numero non corrispondente di istruzioni BEGIN e COMMIT. Conteggio precedente = X, conteggio corrente = Y.

  • Le variabili di tabella, proprio come le variabili normali, non sono vincolate dalle transazioni.


Per quanto riguarda la gestione delle transazioni nei processi che possono essere chiamati in modo indipendente (e quindi necessitano della gestione delle transazioni) o chiamate da altri processi (quindi non necessitano della gestione delle transazioni): questo può essere realizzato in un paio di modi diversi.

Il modo in cui lo gestisco da diversi anni che sembra funzionare bene è solo BEGIN/ COMMIT/ ROLLBACKallo strato più esterno. Le chiamate sub-proc saltano semplicemente i comandi di transazione. Di seguito ho delineato ciò che ho inserito in ogni proc (beh, ognuno che ha bisogno di una gestione delle transazioni).

  • Nella parte superiore di ogni proc, DECLARE @InNestedTransaction BIT;
  • Al posto di semplice BEGIN TRAN, fai:

    IF (@@TRANCOUNT = 0)
    BEGIN
       SET @InNestedTransaction = 0;
       BEGIN TRAN; -- only start a transaction if not already in one
    END;
    ELSE
    BEGIN
       SET @InNestedTransaction = 1;
    END;
  • Al posto di semplice COMMIT, fai:

    IF (@@TRANCOUNT > 0 AND @InNestedTransaction = 0)
    BEGIN
       COMMIT;
    END;
  • Al posto di semplice ROLLBACK, fai:

    IF (@@TRANCOUNT > 0 AND @InNestedTransaction = 0)
    BEGIN
       ROLLBACK;
    END;

Questo metodo dovrebbe funzionare allo stesso modo indipendentemente dal fatto che la transazione sia stata avviata in SQL Server o se sia stata avviata a livello di app.

Per il modello completo di questa gestione delle Transazioni all'interno del TRY...CATCHcostrutto, vedere la mia risposta alla seguente domanda DBA.SE: Siamo tenuti a gestire la Transazione nel Codice C # e nella procedura memorizzata .


Andando oltre le "basi", ci sono alcune sfumature aggiuntive delle transazioni di cui tenere conto:

  • Per impostazione predefinita, le transazioni, nella maggior parte dei casi, non vengono ripristinate / annullate automaticamente quando si verifica un errore. Questo di solito non è un problema, purché tu abbia una corretta gestione degli errori e ROLLBACKti chiami . Tuttavia, a volte le cose si complicano, come nel caso di errori di interruzione batch o quando si utilizza OPENQUERY(o Server collegati in generale) e si verifica un errore sul sistema remoto. Mentre la maggior parte degli errori può essere intrappolata usando TRY...CATCH, ce ne sono due che non possono essere intrappolati in quel modo (non ricordo quali al momento, però - ricercare). In questi casi, è necessario utilizzare SET XACT_ABORT ONper ripristinare correttamente la transazione.

    SET XACT_ABORT ON causa che SQL Server immediatamente roll-back ogni transazione (se uno è attivo) e interruzione del lotto se qualsiasi si verifica un errore. Questa impostazione esisteva prima di SQL Server 2005, che ha introdotto il TRY...CATCHcostrutto. Per la maggior parte, TRY...CATCHgestisce la maggior parte delle situazioni e quindi per lo più oscura la necessità XACT_ABORT ON. Tuttavia, quando si utilizza OPENQUERY(e possibilmente un altro scenario che non ricordo al momento), sarà comunque necessario utilizzare SET XACT_ABORT ON;.

  • All'interno di un trigger, XACT_ABORTè implicitamente impostato su ON. Ciò causa qualsiasi errore all'interno del trigger per annullare l'intera istruzione DML che ha attivato il trigger.

  • Dovresti sempre avere una corretta gestione degli errori, specialmente quando utilizzi Transazioni. Il TRY...CATCHcostrutto, introdotto in SQL Server 2005, fornisce un mezzo per gestire quasi tutte le situazioni, un gradito miglioramento rispetto ai test per @@ERRORogni istruzione successiva, che non ha aiutato molto con errori di interruzione del batch.

    TRY...CATCHintrodotto un nuovo "stato", tuttavia. Quando non si utilizza il TRY...CATCHcostrutto, se si dispone di una Transazione attiva e si verifica un errore, è possibile prendere diversi percorsi:

    • XACT_ABORT OFFe errore di interruzione dell'istruzione: la transazione è ancora attiva e l'elaborazione continua con l'eventuale istruzione successiva .
    • XACT_ABORT OFFe la maggior parte degli errori di interruzione batch: la transazione è ancora attiva e l'elaborazione continua con l'eventuale batch successivo .
    • XACT_ABORT OFFe alcuni errori di interruzione del batch: la transazione viene ripristinata e l'elaborazione continua con l'eventuale batch successivo .
    • XACT_ABORT ONe qualsiasi errore: la transazione viene ripristinata e l'elaborazione continua con il batch successivo , se presente.


    TUTTAVIA, durante l'utilizzo TRY...CATCH, gli errori di interruzione batch non interrompono il batch, ma trasferiscono invece il controllo al CATCHblocco. Quando XACT_ABORTè OFF, l'Operazione sarà ancora attiva la maggior parte del tempo, e sarà necessario COMMIT, o più probabilmente, ROLLBACK. Ma quando si verificano determinati errori di interruzione batch (come con OPENQUERY), o quando lo XACT_ABORTè ON, la Transazione sarà in un nuovo stato, "non credibile". In questo stato non è possibile COMMITné eseguire operazioni DML. Tutto quello che puoi fare è ROLLBACKe SELECTdichiarazioni. Tuttavia, in questo stato "inattendibile", la transazione è stata ripristinata in seguito all'errore che si verifica e l'emissione ROLLBACKè solo una formalità, ma deve essere eseguita.

    Una funzione, XACT_STATE , può essere utilizzata per determinare se una Transazione è attiva, non credibile o non esiste. Si consiglia (almeno da alcuni) di controllare questa funzione nel CATCHblocco per determinare se il risultato è -1(cioè non credibile) invece di testare se @@TRANCOUNT > 0. Ma con XACT_ABORT ONquello, dovrebbe essere l'unico stato possibile in cui trovarsi, quindi sembra che i test per @@TRANCOUNT > 0e XACT_STATE() <> 0siano equivalenti. D'altra parte, quando XACT_ABORTc'è OFFe c'è una Transazione attiva, allora è possibile avere uno stato di uno 1o -1nel CATCHblocco, che consente la possibilità di emettere COMMITinvece di ROLLBACK(anche se, non riesco a pensare a un caso per quando qualcuno vorrebbeCOMMITse la transazione è commettibile). Ulteriori informazioni e ricerche sull'utilizzo XACT_STATE()all'interno di un CATCHblocco con XACT_ABORT ONsono disponibili nella mia risposta alla seguente domanda DBA.SE: in quali casi è possibile eseguire il commit di una transazione dall'interno del blocco CATCH quando XACT_ABORT è impostato su ON? . Si noti che esiste un bug minore XACT_STATE()che lo fa restituire erroneamente 1in alcuni scenari: XACT_STATE () restituisce 1 se utilizzato in SELECT con alcune variabili di sistema ma senza la clausola FROM


Note sul codice originale:

  • È possibile rimuovere il nome assegnato alla transazione in quanto non aiuta nessuno.
  • Non è necessario il BEGINe ENDintorno a ogni EXECchiamata

2
È davvero una buona risposta.
McNets,

1
Wow, questa è una risposta completa! Grazie! A proposito, la seguente pagina risolve gli errori a cui alludi che non sono intrappolati da Try ... Catch? (Sotto il titolo "Errori influenzato da un TRY ... CATCH Construct"? Technet.microsoft.com/en-us/library/ms175976(v=sql.110).aspx
jrdevdba

1
@jrdevdba Grazie :-). E tu benvenuto. Per quanto riguarda gli errori non intrappolati, intendevo praticamente questi due: Compile errors, such as syntax errors, that prevent a batch from runninge Errors that occur during statement-level recompilation, such as object name resolution errors that occur after compilation because of deferred name resolution.. Ma non si verificano molto spesso e quando si trova una situazione del genere, o risolverlo (se si tratta di un bug nel codice) o inserirlo in un processo secondario ( EXECo sp_executesql) in modo che TRY...CATCHpossa intercettarlo.
Solomon Rutzky,

2

Sì, se a causa di un codice di rollback dell'errore nell'istruzione catch della procedura memorizzata principale verrà eseguito, eseguirà il rollback di tutte le operazioni eseguite da qualsiasi istruzione diretta o attraverso una qualsiasi delle procedure memorizzate nidificate in essa.

Anche se non è stata applicata alcuna transazione esplicita nelle procedure memorizzate nidificate, queste procedure memorizzate utilizzeranno la transazione implicita e si impegneranno al completamento MA sia si è eseguito il commit tramite transazione esplicita o implicita nelle procedure memorizzate memorizzate nidificate Il motore SQL Server lo ignorerà e eseguire il rollback di tutte le azioni mediante queste stored procedure nidificate se la stored procedure principale non è riuscita e la transazione è sottoposta a rollback.

Ogni volta che la transazione viene impegnata o ripristinata in base all'azione intrapresa al termine della transazione più esterna. Se viene impegnata la transazione esterna, vengono impegnate anche le transazioni nidificate interne. Se viene eseguito il rollback della transazione esterna, vengono ripristinate anche tutte le transazioni interne, indipendentemente dal fatto che le transazioni interne siano state impegnate singolarmente.

Per riferimento http://technet.microsoft.com/en-us/library/ms189336(v=sql.105).aspx

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.