TL; DR / Executive Summary: Riguardo a questa parte della domanda:
Non vedo in quali casi il controllo può essere passato all'interno CATCH
con 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 1
all'interno di un CATCH
blocco 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_ABORT
ha 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 TRY
blocco. Quelle stesse istruzioni all'interno di una TRY
blocco 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 CATCH
blocco 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
è, ON
eventuali modifiche precedenti vengono automaticamente ripristinate e quindi viene data la possibilità di: a) emettere unROLLBACK
che è principalmente solo un'accettazione della situazione poiché la transazione è già stata ripristinata meno il ripristino @@TRANCOUNT
su 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...CATCH
costrutto che era introdotto in SQL Server 2005. Guardando di nuovo quella documentazione e guardando l'esempio ( senza il TRY...CATCH
), l'utilizzo XACT_ABORT ON
causa 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_ABORT
documentazione).
Penso che sia ragionevole concludere che:
- l'introduzione del
TRY...CATCH
costrutto 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 CATCH
blocco ha davvero senso solo se entrambi i seguenti sono veri:
XACT_ABORT
è OFF
(altrimenti XACT_STATE()
dovrebbe sempre tornare -1
e @@TRANCOUNT
sarebbe tutto ciò di cui hai bisogno)
- Hai una logica nel
CATCH
blocco, o da qualche parte nella catena se le chiamate sono nidificate, che apporta una modifica (una COMMIT
o 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é @@TRANCOUNT
e perché i test dimostrano che il loro ragionamento non viene fuori.
- l'introduzione del
TRY...CATCH
costrutto in SQL Server 2005 ha, per la maggior parte, XACT_ABORT ON
reso 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 ON
fornito un modo semplice e affidabile per interrompere l'elaborazione quando si è verificato un errore, rispetto al controllo @@ERROR
dopo 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() = 1
quando 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 CATCH
blocco 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 COMMIT
o 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 ROLLBACK
perché 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 ON
cambia un po 'le cose. Puoi avere un errore regolare, dopodiché BEGIN TRAN
passerà il controllo al blocco CATCH quando XACT_ABORT
è OFF
e 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 CATCH
blocco come sembra sempre per tornare una -1
quando 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_ABORT
impostato ON
piuttosto 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 ON
e / 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 ON
e passa il controllo al CATCH
blocco con la XACT_STATE()
segnalazione 1
.
Prova questi esempi per vedere l'effetto di XACT_ABORT
sul 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
è OFF
predefinito 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 ON
ovunque, 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 ROLLBACK
gestirlo 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 SqlCommand
oggetto (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
COMMIT
poiché 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 CATCH
blocco immediato , ma prima che il batch termini.
- In un
TRY...CATCH
costrutto, quando lo XACT_ABORT
è OFF
, gli errori che terminerebbero automaticamente la Transazione se si fossero verificati al di fuori di un TRY
blocco, 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 CATCH
blocco, mostrerà a-1
errore di interruzione batch se al momento dell'errore era presente una Transazione attiva.
XACT_STATE()
a volte ritorna 1
anche quando non è presente alcuna Transazione attiva. Se @@SPID
(tra gli altri) è SELECT
nell'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 CATCH
blocco quando XACT_ABORT
è ON
dato il valore restituito sarà sempre -1
.
- Controllo
XACT_STATE()
nel CATCH
blocco quando XACT_ABORT
è OFF
più senso perché il valore restituito almeno avere una certa variazione poiché tornerà 1
per errori istruzione-interruzione. Tuttavia, se codifichi come la maggior parte di noi, questa distinzione non ha senso poiché chiamerai ROLLBACK
comunque semplicemente per il fatto che si è verificato un errore.
- Se si trova una situazione che fa il rilascio di un mandato
COMMIT
in CATCH
blocco, quindi controllare il valore di XACT_STATE()
, ed essere sicuri di SET XACT_ABORT OFF;
.
XACT_ABORT ON
sembra offrire poco o nessun beneficio sul TRY...CATCH
costrutto.
- 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 1
in un CATCH
blocco 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 TRY
blocco annulla automaticamente le modifiche.
- Il
TRY...CATCH
costrutto ha il vantaggio XACT_ABORT ON
di 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 -1
quando 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...EXEC
scenari: 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 1
se utilizzato in SELECT
con @@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 @@TRANCOUNT
effettivamente ritorna 1
in 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 @@TRANCOUNT
all'interno del trigger ha riferito accuratamente 1
anche se nessuna transazione esplicita era stata avviata.
XACT_ABORT
aON
oOFF
.