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:
- 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.
- il check-in
XACT_STATE()in un CATCHblocco ha davvero senso solo se entrambi i seguenti sono veri:
XACT_ABORTè OFF(altrimenti XACT_STATE()dovrebbe sempre tornare -1e @@TRANCOUNTsarebbe tutto ciò di cui hai bisogno)
- 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.
- 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.
- 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:
- Una Transazione "non impegnativa" è una transazione che è stata, per la maggior parte, già ripristinata (le modifiche sono state annullate), ma
@@TRANCOUNTè ancora> 0.
- 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.
- 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.
- 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).
XACT_STATE(), almeno in un CATCHblocco, mostrerà a-1 errore di interruzione batch se al momento dell'errore era presente una Transazione attiva.
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.
XACT_ABORTaONoOFF.