In quali casi è possibile eseguire il commit di una transazione dall'interno del blocco CATCH quando XACT_ABORT è impostato su ON?


13

Ho letto MSDN su TRY...CATCHe XACT_STATE.

Ha il seguente esempio che utilizza XACT_STATEnel CATCHblocco di un TRY…CATCHcostrutto per determinare se eseguire il commit o il rollback di una transazione:

USE AdventureWorks2012;
GO

-- SET XACT_ABORT ON will render the transaction uncommittable
-- when the constraint violation occurs.
SET XACT_ABORT ON;

BEGIN TRY
    BEGIN TRANSACTION;
        -- A FOREIGN KEY constraint exists on this table. This 
        -- statement will generate a constraint violation error.
        DELETE FROM Production.Product
            WHERE ProductID = 980;

    -- If the delete operation succeeds, commit the transaction. The CATCH
    -- block will not execute.
    COMMIT TRANSACTION;
END TRY
BEGIN CATCH
    -- Test XACT_STATE for 0, 1, or -1.
    -- If 1, the transaction is committable.
    -- If -1, the transaction is uncommittable and should 
    --     be rolled back.
    -- XACT_STATE = 0 means there is no transaction and
    --     a commit or rollback operation would generate an error.

    -- Test whether the transaction is uncommittable.
    IF (XACT_STATE()) = -1
    BEGIN
        PRINT 'The transaction is in an uncommittable state.' +
              ' Rolling back transaction.'
        ROLLBACK TRANSACTION;
    END;

    -- Test whether the transaction is active and valid.
    IF (XACT_STATE()) = 1
    BEGIN
        PRINT 'The transaction is committable.' + 
              ' Committing transaction.'
        COMMIT TRANSACTION;   
    END;
END CATCH;
GO

Quello che non capisco è, perché dovrei preoccuparmi e controllare cosa XACT_STATEritorna?

Si noti che la bandiera XACT_ABORTè impostata ONnell'esempio.

Se c'è un errore abbastanza grave all'interno del TRYblocco, il controllo passerà CATCH. Quindi, se sono dentro CATCH, so che la transazione ha avuto un problema e davvero l'unica cosa sensata da fare in questo caso è di ripristinarla, no?

Ma questo esempio di MSDN implica che possono esserci casi in cui viene passato il controllo CATCHe ha ancora senso impegnare la transazione. Qualcuno potrebbe fornire qualche esempio pratico quando può accadere, quando ha senso?

Non vedo in quali casi il controllo può essere passato all'interno CATCHcon una transazione che può essere impegnata quando XACT_ABORTè impostata suON .

L'articolo di MSDN su SET XACT_ABORTha un esempio quando alcune istruzioni all'interno di una transazione vengono eseguite correttamente e alcune non riescono quando XACT_ABORTè impostata su OFF, lo capisco. Ma, SET XACT_ABORT ONcome può accadere che XACT_STATE()ritorni 1 all'interno del CATCHblocco?

Inizialmente, avrei scritto questo codice in questo modo:

USE AdventureWorks2012;
GO

-- SET XACT_ABORT ON will render the transaction uncommittable
-- when the constraint violation occurs.
SET XACT_ABORT ON;

BEGIN TRY
    BEGIN TRANSACTION;
        -- A FOREIGN KEY constraint exists on this table. This 
        -- statement will generate a constraint violation error.
        DELETE FROM Production.Product
            WHERE ProductID = 980;

    -- If the delete operation succeeds, commit the transaction. The CATCH
    -- block will not execute.
    COMMIT TRANSACTION;
END TRY
BEGIN CATCH
    -- Some severe problem with the transaction
    PRINT 'Rolling back transaction.';
    ROLLBACK TRANSACTION;
END CATCH;
GO

Tenendo conto di una risposta di Max Vernon, scriverei il codice in questo modo. Ha dimostrato che ha senso verificare se esiste una transazione attiva prima di tentare ROLLBACK. Tuttavia, con SET XACT_ABORT ONil CATCHblocco può avere una transazione condannata o nessuna transazione. Quindi, in ogni caso, non c'è niente da fare COMMIT. Ho sbagliato?

USE AdventureWorks2012;
GO

-- SET XACT_ABORT ON will render the transaction uncommittable
-- when the constraint violation occurs.
SET XACT_ABORT ON;

BEGIN TRY
    BEGIN TRANSACTION;
        -- A FOREIGN KEY constraint exists on this table. This 
        -- statement will generate a constraint violation error.
        DELETE FROM Production.Product
            WHERE ProductID = 980;

    -- If the delete operation succeeds, commit the transaction. The CATCH
    -- block will not execute.
    COMMIT TRANSACTION;
END TRY
BEGIN CATCH
    -- Some severe problem with the transaction
    IF (XACT_STATE()) <> 0
    BEGIN
        -- There is still an active transaction that should be rolled back
        PRINT 'Rolling back transaction.';
        ROLLBACK TRANSACTION;
    END;

END CATCH;
GO

Risposte:


8

Si scopre che la transazione non può essere impegnata dall'interno del CATCHblocco se XACT_ABORTè impostata su ON.

L'esempio di MSDN è in qualche modo fuorviante, poiché il controllo implica che XACT_STATEin alcuni casi può restituire 1 e potrebbe essere possibile COMMITla transazione.

IF (XACT_STATE()) = 1
BEGIN
    PRINT 'The transaction is committable.' + 
          ' Committing transaction.'
    COMMIT TRANSACTION;   
END;

Non è vero, XACT_STATEnon restituirà mai 1 CATCHblocco interno se XACT_ABORTimpostato su ON.

Sembra che il codice di esempio MSDN fosse pensato principalmente per illustrare l'uso della XACT_STATE()funzione indipendentemente XACT_ABORTdall'impostazione. Il codice di esempio sembra abbastanza generico per lavoro sia XACT_ABORTimpostato su ONe OFF. È solo che con XACT_ABORT = ONil controllo IF (XACT_STATE()) = 1diventa superfluo.


Esiste una serie molto dettagliata di articoli sull'errore e la gestione delle transazioni in SQL Server di Erland Sommarskog. Nella parte 2 - Classificazione degli errori presenta una tabella completa che mette insieme tutte le classi di errori e il modo in cui sono gestiti da SQL Server e come TRY ... CATCHe XACT_ABORTcambia il comportamento.

+-----------------------------+---------------------------++------------------------------+
|                             |     Without TRY-CATCH     ||        With TRY-CATCH        |
+-----------------------------+-------+-------+-----+-----++------------------+-----+-----+
|              SET XACT_ABORT |  OFF  |  ON   | OFF | ON  ||    ON or OFF     | OFF | ON  |
+-----------------------------+-------+-------+-----+-----++------------------+-----+-----+
| Class Name                  |    Aborts     |   Rolls   ||    Catchable     |   Dooms   |
|                             |               |   Back    ||                  |transaction|
+-----------------------------+-------+-------+-----+-----++------------------+-----+-----+
| Fatal errors                |  Connection   |    Yes    ||       No         |    n/a    |
| Batch-aborting              |     Batch     |    Yes    ||       Yes        |    Yes    |
| Batch-only aborting         |     Batch     | No  | Yes ||       Yes        | No  | Yes |
| Statement-terminating       | Stmnt | Batch | No  | Yes ||       Yes        | No  | Yes |
| Terminates nothing at all   |    Nothing    |    No     ||       Yes        | No  | Yes |
| Compilation: syntax errors  |  (Statement)  |    No     ||       Yes        | No  | Yes |
| Compilation: binding errors | Scope | Batch | No  | Yes || Outer scope only | No  | Yes |
| Compilation: optimisation   |     Batch     |    Yes    || Outer scope only |    Yes    |
| Attention signal            |     Batch     | No  | Yes ||       No         |    n/a    |
| Informational/warning msgs  |    Nothing    |    No     ||       No         |    n/a    |
| Uncatchable errors          |    Varying    |  Varying  ||       No         |    n/a    |
+-----------------------------+-------+-------+-----+-----++------------------+-----+-----+

L'ultima colonna della tabella risponde alla domanda. Con TRY-CATCHe con XACT_ABORT ONla transazione è condannata in tutti i casi possibili.

Una nota al di fuori dell'ambito della domanda. Come dice Erland, questa coerenza è uno dei motivi per impostare XACT_ABORTa ON:

Ho già dato la raccomandazione che le procedure memorizzate dovrebbero includere il comando SET XACT_ABORT, NOCOUNT ON. Se guardi la tabella sopra, vedi che con XACT_ABORTin effetti, c'è un livello più alto di coerenza. Ad esempio, la transazione è sempre condannata. Di seguito, vi mostrerò molti esempi in cui ho impostato XACT_ABORTa OFF, in modo che si può ottenere una comprensione del perché si dovrebbe evitare questa impostazione predefinita.


7

Mi avvicinerei a questo in modo diverso. XACT_ABORT_ONè una mazza, puoi usare un approccio più raffinato, vedi Gestione delle eccezioni e transazioni nidificate :

create procedure [usp_my_procedure_name]
as
begin
    set nocount on;
    declare @trancount int;
    set @trancount = @@trancount;
    begin try
        if @trancount = 0
            begin transaction
        else
            save transaction usp_my_procedure_name;

        -- Do the actual work here

lbexit:
        if @trancount = 0   
            commit;
    end try
    begin catch
        declare @error int, @message varchar(4000), @xstate int;
        select @error = ERROR_NUMBER(), @message = ERROR_MESSAGE(), @xstate = XACT_STATE();
        if @xstate = -1
            rollback;
        if @xstate = 1 and @trancount = 0
            rollback
        if @xstate = 1 and @trancount > 0
            rollback transaction usp_my_procedure_name;

        raiserror ('usp_my_procedure_name: %d: %s', 16, 1, @error, @message) ;
    end catch   
end
go

Questo approccio eseguirà il rollback, quando possibile, solo del lavoro eseguito all'interno del blocco TRY e ripristinerà lo stato prima di entrare nel blocco TRY. In questo modo è possibile eseguire elaborazioni complesse, come iterare un cursore, senza perdere tutto il lavoro in caso di errore. L'unico inconveniente è che, utilizzando i punti di salvataggio delle transazioni, è vietato utilizzare qualsiasi cosa incompatibile con i punti di salvataggio, come le transazioni distribuite.


Apprezzo la risposta, ma la questione non è realmente se dovremmo impostare XACT_ABORTa ONo OFF.
Vladimir Baranov,

7

TL; DR / Executive Summary: Riguardo a questa parte della domanda:

Non vedo in quali casi il controllo può essere passato all'interno CATCHcon una transazione che può essere impegnata quando XACT_ABORTè impostata suON .

Ho fatto un bel po 'di test su questo ora e non riesco a trovare alcun caso in cui XACT_STATE()ritorni 1all'interno di un CATCHblocco quando @@TRANCOUNT > 0 e la proprietà session di XACT_ABORTè ON. E infatti, secondo l'attuale pagina MSDN per SET XACT_ABORT :

Quando SET XACT_ABORT è ON, se un'istruzione Transact-SQL genera un errore di runtime, l'intera transazione viene terminata e ripristinata.

Tale affermazione sembra essere in accordo con la tua speculazione e le mie conclusioni.

L'articolo MSDN su SET XACT_ABORTha un esempio quando alcune istruzioni all'interno di una transazione vengono eseguite correttamente e altre falliscono quando XACT_ABORTè impostato suOFF

Vero, ma le istruzioni in quell'esempio non sono all'interno di un TRYblocco. Quelle stesse istruzioni all'interno di una TRYblocco sarebbe ancora impediscono l'esecuzione di eventuali dichiarazioni dopo quello che ha causato l'errore, ma supponendo che XACT_ABORTè OFF, quando il controllo viene passato al CATCHblocco della Transazione è ancora fisicamente valida dal fatto che tutte le precedenti modifiche accaduto senza errore e può essere commesso, se questo è il desiderio, oppure possono essere ritirati. D'altra parte, se lo XACT_ABORTè, ONeventuali modifiche precedenti vengono automaticamente ripristinate e quindi viene data la possibilità di: a) emettere unROLLBACKche è principalmente solo un'accettazione della situazione poiché la transazione è già stata ripristinata meno il ripristino @@TRANCOUNTsu 0, oppure b) viene visualizzato un errore. Non c'è molta scelta, vero?

Un dettaglio forse importante per questo puzzle che non è evidente in quella documentazione per SET XACT_ABORTè che questa proprietà di sessione, e anche quel codice di esempio, è in circolazione da SQL Server 2000 (la documentazione è quasi identica tra le versioni), che precede il TRY...CATCHcostrutto che era introdotto in SQL Server 2005. Guardando di nuovo quella documentazione e guardando l'esempio ( senza il TRY...CATCH), l'utilizzo XACT_ABORT ONcausa un rollback immediato della Transazione: non esiste uno stato Transaction di "non impegnabile" (si noti che non è menzionato in tutto uno stato di transazione "non vincolante" in quella SET XACT_ABORTdocumentazione).

Penso che sia ragionevole concludere che:

  1. l'introduzione del TRY...CATCHcostrutto in SQL Server 2005 ha creato la necessità di un nuovo stato Transaction (ovvero "non vincolante") e la XACT_STATE()funzione per ottenere tali informazioni.
  2. il check-in XACT_STATE()in un CATCHblocco ha davvero senso solo se entrambi i seguenti sono veri:
    1. XACT_ABORTè OFF(altrimenti XACT_STATE()dovrebbe sempre tornare -1e @@TRANCOUNTsarebbe tutto ciò di cui hai bisogno)
    2. Hai una logica nel CATCHblocco, o da qualche parte nella catena se le chiamate sono nidificate, che apporta una modifica (una COMMITo anche qualsiasi istruzione DML, DDL, ecc.) Invece di fare una ROLLBACK. (si tratta di un caso d'uso molto atipico) ** si prega di consultare la nota in fondo, nella sezione AGGIORNAMENTO 3, relativa a una raccomandazione non ufficiale da parte di Microsoft di verificare sempre XACT_STATE()anziché @@TRANCOUNTe perché i test dimostrano che il loro ragionamento non viene fuori.
  3. l'introduzione del TRY...CATCHcostrutto in SQL Server 2005 ha, per la maggior parte, XACT_ABORT ONreso obsoleta la proprietà della sessione in quanto prevede un maggior grado di controllo sulla Transazione (almeno si ha l'opzione COMMIT, a condizione che XACT_STATE()non ritorni -1).
    Un altro modo per esaminare questo è, prima di SQL Server 2005 , XACT_ABORT ONfornito un modo semplice e affidabile per interrompere l'elaborazione quando si è verificato un errore, rispetto al controllo @@ERRORdopo ogni istruzione.
  4. Il codice di esempio della documentazione per XACT_STATE()è errato, o nella migliore delle ipotesi fuorviante, in quanto mostra il controllo di XACT_STATE() = 1quando lo XACT_ABORTè ON.

La parte lunga ;-)

Sì, quel codice di esempio su MSDN è un po 'confuso (vedi anche: @@ TRANCOUNT (Rollback) vs. XACT_STATE ) ;-). E ritengo che sia fuorviante perché mostra qualcosa che non ha senso (per il motivo di cui ti stai chiedendo: puoi anche avere una transazione "commettibile" nel CATCHblocco quando XACT_ABORTè ON), o anche se è possibile, si concentra ancora su una possibilità tecnica che pochi vorranno o avranno mai bisogno, e ignora il motivo per cui è più probabile che ne abbia bisogno.

Se c'è un errore abbastanza grave all'interno del blocco TRY, il controllo passerà in CATCH. Quindi, se sono all'interno di CATCH, so che la transazione ha avuto un problema e davvero l'unica cosa sensata da fare in questo caso è di ripristinarla, no?

Penso che sarebbe di aiuto se ci assicurassimo di essere sulla stessa pagina per quanto riguarda ciò che si intende con determinate parole e concetti:

  • "errore abbastanza grave": solo per essere chiari, PROVA ... CATCH intrappolerà la maggior parte degli errori. L'elenco di ciò che non verrà rilevato è elencato nella pagina MSDN collegata, nella sezione "Errori non interessati da un costrutto TRY ... CATCH".

  • "Se sono all'interno del CATCH, so che operazione ha avuto un problema" (em PHA è aggiunta): Se per "transazione" si intende la logica unità di lavoro come determinato da voi raggruppando le dichiarazioni in una transazione esplicita, poi molto probabilmente sì. Penso che la maggior parte di noi DB tenderà a concordare sul fatto che il rollback sia "l'unica cosa sensata da fare" poiché probabilmente abbiamo una visione simile di come e perché utilizziamo transazioni esplicite e concepiamo quali passi dovrebbero costituire un'unità atomica di lavoro.

    Ma, se intendi le unità di lavoro effettive che vengono raggruppate nella transazione esplicita, allora no, non sai che la transazione stessa ha avuto un problema. Sai solo che una dichiarazione in esecuzione all'interno della transazione definita esplicitamente ha sollevato un errore. Ma potrebbe non essere un'istruzione DML o DDL. E anche se fosse un'istruzione DML, la Transazione stessa potrebbe essere comunque commettibile.

Dati i due punti sopra indicati, dovremmo probabilmente fare una distinzione tra le transazioni che "non puoi" impegnare e quelle che "non vuoi" impegnare.

Quando XACT_STATE()restituisce a 1, significa che la transazione è "commettibile", che è possibile scegliere tra COMMITo ROLLBACK. Potresti non volerlo impegnare, ma se per qualche difficile-anche-venire-con-un-esempio-per il motivo che volevi, almeno potresti farlo perché alcune parti della Transazione sono state completate correttamente.

Ma quando XACT_STATE()ritorna a -1, allora è davvero necessario ROLLBACKperché una parte della Transazione è andata in cattivo stato. Ora, sono d'accordo sul fatto che se il controllo è stato passato al blocco CATCH, allora ha senso verificare semplicemente @@TRANCOUNT, perché anche se potessi eseguire il commit della Transazione, perché dovresti farlo?

Ma se noti nella parte superiore dell'esempio, l'impostazione di XACT_ABORT ONcambia un po 'le cose. Puoi avere un errore regolare, dopodiché BEGIN TRANpasserà il controllo al blocco CATCH quando XACT_ABORTè OFFe XACT_STATE () tornerà 1. MA, se XACT_ABORT lo è ON, la Transazione viene "interrotta" (ovvero non valida) per qualsiasi errore precedente e quindiXACT_STATE() tornerà -1. In questo caso, sembra inutile controllare XACT_STATE()all'interno del CATCHblocco come sembra sempre per tornare una -1quando XACT_ABORTè ON.

Allora a cosa serve XACT_STATE()? Alcuni indizi sono:

  • La pagina MSDN per TRY...CATCH, nella sezione "Transazioni non impegnabili e XACT_STATE", dice:

    Un errore che di solito termina una transazione al di fuori di un blocco TRY fa sì che una transazione entri in uno stato non impegnabile quando si verifica l'errore all'interno di un blocco TRY.

  • La pagina MSDN per SET XACT_ABORT , nella sezione "Note", dice:

    Quando SET XACT_ABORT è OFF, in alcuni casi viene eseguito il rollback solo dell'istruzione Transact-SQL che ha generato l'errore e la transazione continua l'elaborazione.

    e:

    XACT_ABORT deve essere impostato su ON per le dichiarazioni di modifica dei dati in una transazione implicita o esplicita contro la maggior parte dei provider OLE DB, incluso SQL Server.

  • La pagina MSDN per INIZIA TRANSAZIONE , nella sezione "Note", dice:

    La transazione locale avviata dall'istruzione BEGIN TRANSACTION viene convertita in una transazione distribuita se vengono eseguite le seguenti azioni prima che venga eseguito il commit o il rollback dell'istruzione:

    • Viene eseguita un'istruzione INSERT, DELETE o UPDATE che fa riferimento a una tabella remota su un server collegato. L'istruzione INSERT, UPDATE o DELETE ha esito negativo se il provider OLE DB utilizzato per accedere al server collegato non supporta l'interfaccia ITransactionJoin.

L'utilizzo più applicabile sembra essere nel contesto delle istruzioni DML del server collegato. E credo di essermi imbattuto in questo io anni fa. Non ricordo tutti i dettagli, ma aveva qualcosa a che fare con il server remoto non disponibile e, per qualche motivo, quell'errore non è stato intercettato nel blocco TRY e non è mai stato inviato al CATCH e così ha fatto un COMMIT quando non avrebbe dovuto. Certo, quello avrebbe potuto essere un problema di non aver XACT_ABORTimpostato ONpiuttosto che non riuscire a controllare XACT_STATE(), o forse entrambi. E ricordo di aver letto qualcosa che diceva che se si utilizzano server collegati e / o transazioni distribuite, è necessario utilizzare XACT_ABORT ONe / o XACT_STATE(), ma non riesco a trovare quel documento ora. Se lo trovo, lo aggiornerò con il link.

Tuttavia, ho provato diverse cose e non riesco a trovare uno scenario che ha XACT_ABORT ONe passa il controllo al CATCHblocco con la XACT_STATE()segnalazione 1.

Prova questi esempi per vedere l'effetto di XACT_ABORTsul valore di XACT_STATE():

SET XACT_ABORT OFF;

BEGIN TRY
    BEGIN TRAN;

    SELECT 1/0 AS [DivideByZero]; -- error, yo!

    COMMIT TRAN;
END TRY
BEGIN CATCH
    SELECT @@TRANCOUNT AS [@@TRANCOUNT],
            XACT_STATE() AS [XactState],
            ERROR_MESSAGE() AS [ErrorMessage]

    IF (@@TRANCOUNT > 0)
    BEGIN
        ROLLBACK;
    END;
END CATCH;

GO ------------------------------------------------

SET XACT_ABORT ON;

BEGIN TRY
    BEGIN TRAN;

    SELECT 1/0 AS [DivideByZero]; -- error, yo!

    COMMIT TRAN;
END TRY
BEGIN CATCH
    SELECT @@TRANCOUNT AS [@@TRANCOUNT],
            XACT_STATE() AS [XactState],
            ERROR_MESSAGE() AS [ErrorMessage]

    IF (@@TRANCOUNT > 0)
    BEGIN
        ROLLBACK;
    END;
END CATCH;

GO ------------------------------------------------

SET XACT_ABORT ON;

BEGIN TRY
    SELECT 1/0 AS [DivideByZero]; -- error, yo!
END TRY
BEGIN CATCH
    SELECT @@TRANCOUNT AS [@@TRANCOUNT],
            XACT_STATE() AS [XactState],
            ERROR_MESSAGE() AS [ErrorMessage]
END CATCH;

AGGIORNARE

Pur non facendo parte della domanda originale, sulla base di questi commenti su questa risposta:

Ho letto gli articoli di Erland sulla gestione degli errori e delle transazioni in cui afferma che XACT_ABORTè OFFpredefinito per motivi legacy e normalmente dovremmo impostarlo su ON.
...
"... se segui la raccomandazione ed esegui SET XACT_ABORT ON, la transazione sarà sempre condannata."

Prima di utilizzare XACT_ABORT ONovunque, vorrei porre una domanda: che cosa si ottiene esattamente qui? Non ho trovato necessario farlo e in generale sostengo che dovresti usarlo solo quando necessario. Se vuoi o meno puoi ROLLBACKgestirlo abbastanza facilmente usando il modello mostrato in @ Remus's risposta di , o quello che sto usando da anni che è essenzialmente la stessa cosa ma senza il punto di salvataggio, come mostrato in questa risposta (che gestisce le chiamate nidificate):

Siamo tenuti a gestire la transazione nel codice C # e nella procedura memorizzata


AGGIORNAMENTO 2

Ho fatto un po 'più di test, questa volta creando una piccola app .NET Console, creando una Transazione nel livello dell'app, prima di eseguire qualsiasi SqlCommandoggetto (ad es. Tramite using (SqlTransaction _Tran = _Connection.BeginTransaction()) { ...), oltre a utilizzare un errore di interruzione batch anziché solo un'istruzione errore di interruzione e rilevato che:

  1. Una Transazione "non impegnativa" è una transazione che è stata, per la maggior parte, già ripristinata (le modifiche sono state annullate), ma @@TRANCOUNTè ancora> 0.
  2. Quando si dispone di una transazione "non vincolante", non è possibile emettere una COMMITpoiché ciò genererà un errore che indica che la transazione è "non vincolante". Inoltre, non puoi ignorarlo / non fare nulla poiché verrà generato un errore quando il batch termina affermando che il batch è stato completato con una transazione persistente e non impegnabile e verrà eseguito il rollback (quindi, um, se eseguirà il rollback automatico comunque, perché preoccuparsi di lanciare l'errore?). Quindi è necessario emettere un esplicito ROLLBACK, forse non nel CATCHblocco immediato , ma prima che il batch termini.
  3. In un TRY...CATCHcostrutto, quando lo XACT_ABORTè OFF, gli errori che terminerebbero automaticamente la Transazione se si fossero verificati al di fuori di un TRYblocco, come errori di interruzione in batch, annulleranno il lavoro ma non termineranno la Transazione, lasciandola come "non credibile". Emissione aROLLBACK è più una formalità necessaria per chiudere l'Operazione, ma il lavoro è già stato sottoposto a rollback.
  4. Quando XACT_ABORTèON , la maggior parte degli errori agisce come interruzione batch e quindi si comporta come descritto nel punto elenco sopra (n. 3).
  5. XACT_STATE(), almeno in un CATCHblocco, mostrerà a-1 errore di interruzione batch se al momento dell'errore era presente una Transazione attiva.
  6. XACT_STATE()a volte ritorna 1anche quando non è presente alcuna Transazione attiva. Se @@SPID(tra gli altri) è SELECTnell'elenco insieme a XACT_STATE(), quindi XACT_STATE()restituirà 1 quando non è presente alcuna Transazione attiva. Questo comportamento è iniziato in SQL Server 2012 ed esiste nel 2014, ma non l'ho testato nel 2016.

Tenendo presente i punti precedenti:

  • Punti Attribuite # 4 e # 5, poiché la maggior parte (o tutti?) Errori renderanno una transazione "uncommitable", sembra del tutto inutile per controllare XACT_STATE()in CATCHblocco quando XACT_ABORTè ONdato il valore restituito sarà sempre -1.
  • Controllo XACT_STATE()nel CATCHblocco quando XACT_ABORTè OFFpiù senso perché il valore restituito almeno avere una certa variazione poiché tornerà 1per errori istruzione-interruzione. Tuttavia, se codifichi come la maggior parte di noi, questa distinzione non ha senso poiché chiamerai ROLLBACKcomunque semplicemente per il fatto che si è verificato un errore.
  • Se si trova una situazione che fa il rilascio di un mandato COMMITin CATCHblocco, quindi controllare il valore di XACT_STATE(), ed essere sicuri di SET XACT_ABORT OFF;.
  • XACT_ABORT ONsembra offrire poco o nessun beneficio sul TRY...CATCHcostrutto.
  • Non riesco a trovare uno scenario in cui il controllo XACT_STATE()offra un vantaggio significativo rispetto al semplice controllo @@TRANCOUNT.
  • Non riesco a trovare nessuno scenario in cui XACT_STATE()ritorni 1in un CATCHblocco quando lo XACT_ABORTè ON. Penso che sia un errore di documentazione.
  • Sì, è possibile ripristinare una Transazione che non è stata avviata in modo esplicito. E nel contesto dell'uso XACT_ABORT ON, è un punto controverso poiché un errore che si verifica in un TRYblocco annulla automaticamente le modifiche.
  • Il TRY...CATCHcostrutto ha il vantaggio XACT_ABORT ONdi non annullare automaticamente l'intera Transazione, e quindi consentire alla Transazione (purché i XACT_STATE()rendimenti 1) di essere impegnata (anche se si tratta di un caso limite).

Esempio di XACT_STATE()ritorno -1quando XACT_ABORTè OFF:

SET XACT_ABORT OFF;

BEGIN TRY
    BEGIN TRAN;

    SELECT CONVERT(INT, 'g') AS [ConversionError];

    COMMIT TRAN;
END TRY
BEGIN CATCH
    DECLARE @State INT;
    SET @State = XACT_STATE();
    SELECT @@TRANCOUNT AS [@@TRANCOUNT],
            @State AS [XactState],
            ERROR_MESSAGE() AS [ErrorMessage];

    IF (@@TRANCOUNT > 0)
    BEGIN
        SELECT 'Rollin back...' AS [Transaction];
        ROLLBACK;
    END;
END CATCH;

AGGIORNAMENTO 3

Relativo all'articolo n. 6 nella sezione AGGIORNAMENTO 2 (ovvero possibile valore errato restituito da XACT_STATE()quando non è presente alcuna Transazione attiva):

  • Il comportamento strano / errato è iniziato in SQL Server 2012 (finora testato rispetto a 2012 SP2 e 2014 SP1)
  • Nelle versioni di SQL Server 2005, 2008 e 2008 R2, XACT_STATE()non venivano riportati i valori previsti se utilizzati in Trigger o INSERT...EXECscenari: xact_state () non può essere utilizzato in modo affidabile per determinare se una transazione è condannata . Tuttavia, in queste 3 versioni (ho provato solo su 2008 R2), XACT_STATE()non viene riportato in modo errato 1se utilizzato in SELECTcon @@SPID.
  • Esiste un bug di Connect archiviato per il comportamento menzionato qui ma viene chiuso come "In base alla progettazione": XACT_STATE () può restituire uno stato di transazione errato in SQL 2012 . Tuttavia, il test è stato eseguito quando si selezionava da un DMV e si è concluso che ciò avrebbe naturalmente comportato una transazione generata dal sistema, almeno per alcuni DMV. Nella risposta finale degli Stati membri è stato inoltre affermato che:

    Notare che un'istruzione IF, e anche un SELECT senza FROM, non avviano una transazione.
    ad esempio, l'esecuzione di SELECT XACT_STATE () se non si dispone di una transazione esistente in precedenza restituirà 0.

    Tali affermazioni sono errate dato il seguente esempio:

    SELECT @@TRANCOUNT AS [TRANCOUNT], XACT_STATE() AS [XACT_STATE], @@SPID AS [SPID];
    GO
    DECLARE @SPID INT;
    SET @SPID = @@SPID;
    SELECT @@TRANCOUNT AS [TRANCOUNT], XACT_STATE() AS [XACT_STATE], @SPID AS [SPID];
    GO
  • Pertanto, il nuovo bug di Connect:
    XACT_STATE () restituisce 1 se utilizzato in SELECT con alcune variabili di sistema ma senza la clausola FROM

Si noti che in "XACT_STATE () può restituire uno stato di transazione errato in SQL 2012" Elemento di connessione collegato direttamente sopra, Microsoft (bene, un rappresentante di) afferma:

@@ trancount restituisce il numero di istruzioni BEGIN TRAN. Pertanto, non è un indicatore affidabile dell'esistenza di una transazione attiva. XACT_STATE () restituisce anche 1 se è presente una transazione di autocommit attiva ed è quindi un indicatore più affidabile della presenza di una transazione attiva.

Tuttavia, non riesco a trovare alcun motivo per non fidarmi @@TRANCOUNT. Il seguente test mostra che @@TRANCOUNTeffettivamente ritorna 1in una transazione con commit automatico:

--- begin setup
GO
CREATE PROCEDURE #TransactionInfo AS
SET NOCOUNT ON;
SELECT @@TRANCOUNT AS [TranCount],
       XACT_STATE() AS [XactState];
GO
--- end setup

DECLARE @Test TABLE (TranCount INT, XactState INT);

SELECT * FROM @Test; -- no rows

EXEC #TransactionInfo; -- 0 for both fields

INSERT INTO @Test (TranCount, XactState)
    EXEC #TransactionInfo;

SELECT * FROM @Test; -- 1 row; 1 for both fields

Ho anche testato su una tabella reale con un trigger e @@TRANCOUNTall'interno del trigger ha riferito accuratamente 1anche se nessuna transazione esplicita era stata avviata.


4

La programmazione difensiva richiede di scrivere codice che gestisca il maggior numero possibile di stati noti, riducendo così la possibilità di bug.

Controllare XACT_STATE () per determinare se è possibile eseguire un rollback è semplicemente una buona pratica. Tentare ciecamente di eseguire il rollback significa che potresti inavvertitamente causare un errore all'interno di TRY ... CATCH.

Un modo in cui un rollback potrebbe non riuscire all'interno di un TRY ... CATCH sarebbe se non avessi avviato esplicitamente una transazione. Copiare e incollare blocchi di codice potrebbe facilmente causare questo.


Grazie per la risposta. Non riuscivo a pensare a un caso in cui semplice ROLLBACKnon avrebbe funzionato all'interno CATCHe tu hai dato un buon esempio. Immagino, può anche diventare rapidamente disordinato se TRY ... CATCH ... ROLLBACKsono coinvolte transazioni nidificate e stored procedure nidificate con le proprie .
Vladimir Baranov,

Tuttavia, lo apprezzerei se tu potessi estendere la tua risposta per quanto riguarda la seconda parte: IF (XACT_STATE()) = 1 COMMIT TRANSACTION; come possiamo finire all'interno del CATCHblocco con una transazione vincolante? Non oserei commettere un po '(possibile) immondizia dall'interno CATCH. Il mio ragionamento è: se siamo dentro CATCHqualcosa che è andato storto, non posso fidarmi dello stato del database, quindi farei meglio ROLLBACKqualunque cosa abbiamo.
Vladimir Baranov,
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.