Ich habe MSDN über TRY...CATCH
und gelesen XACT_STATE
.
Das folgende Beispiel bestimmt XACT_STATE
im CATCH
Block eines TRY…CATCH
Konstrukts, ob eine Transaktion festgeschrieben oder zurückgesetzt werden soll:
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
Was ich nicht verstehe ist, warum sollte ich mich darum kümmern und prüfen, was XACT_STATE
zurückkommt?
Bitte beachten Sie, dass im Beispiel das Flag gesetzt XACT_ABORT
ist ON
.
Wenn im TRY
Block ein schwerwiegender Fehler vorliegt , wird die Steuerung an übergeben CATCH
. Also, wenn ich in der bin CATCH
, weiß ich, dass die Transaktion ein Problem hatte und das einzig vernünftige, was in diesem Fall zu tun ist, ist, sie zurückzusetzen, nicht wahr?
Dieses Beispiel von MSDN impliziert jedoch, dass es Fälle geben kann, in denen die Steuerung übergeben wird, CATCH
und es dennoch sinnvoll ist, die Transaktion festzuschreiben. Könnte jemand ein praktisches Beispiel geben, wann es passieren kann, wann es Sinn macht?
Ich sehe nicht, in welchen Fällen das Steuerelement CATCH
mit einer Transaktion übergeben werden kann, die festgeschrieben werden kann, wenn auf festgelegt XACT_ABORT
istON
.
Der MSDN-Artikel über SET XACT_ABORT
hat ein Beispiel, in dem einige Anweisungen in einer Transaktion erfolgreich ausgeführt werden und andere fehlschlagen, wenn auf XACT_ABORT
festgelegt OFF
ist. Ich verstehe das. Aber SET XACT_ABORT ON
wie kann es passieren, dass XACT_STATE()
1 innerhalb des CATCH
Blocks zurückgegeben wird?
Anfangs hätte ich diesen Code so geschrieben:
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
Unter Berücksichtigung einer Antwort von Max Vernon würde ich den Code so schreiben. Er zeigte, dass es sinnvoll ist, vor dem Versuch zu prüfen, ob eine Transaktion aktiv ist ROLLBACK
. Trotzdem kann mit SET XACT_ABORT ON
dem CATCH
Block entweder eine Transaktion zum Scheitern verurteilt sein oder gar keine Transaktion. Es gibt also auf jeden Fall nichts zu tun COMMIT
. Liege ich falsch?
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
quelle
XACT_ABORT
aufON
oderOFF
.TL; DR / Zusammenfassung: Zu diesem Teil der Frage:
Ich habe jetzt auf diese ziemlich viel Prüfung getan und ich kann keine Fälle , wo
XACT_STATE()
Renditen1
innerhalb einesCATCH
Blocks , wenn@@TRANCOUNT > 0
und der Sitzungs EigenschaftXACT_ABORT
istON
. Und tatsächlich laut der aktuellen MSDN-Seite für SET XACT_ABORT :Diese Aussage scheint mit Ihrer Spekulation und meinen Ergebnissen übereinzustimmen.
Richtig, aber die Anweisungen in diesem Beispiel befinden sich nicht in einem
TRY
Block. Die gleichen Aussagen innerhalb einesTRY
Blocks würde verhindern , dass nach wie vor der Ausführung für Aussagen nach der, die den Fehler verursacht hat , aber davon aus, dassXACT_ABORT
istOFF
, wenn die Steuerung an die übergeben wirdCATCH
Block die Transaktion noch in physisch gültig ist , dass alle vorherigen Änderungen ohne Fehler geschah und kann begangen werden, wenn das der Wunsch ist, oder sie können zurückgesetzt werden. Auf der anderen Seite, wennXACT_ABORT
istON
dann werden alle Änderungen automatisch vor Rollback, und dann haben Sie die Wahl, entweder gegeben: a) Ausgabe einerROLLBACK
Dies ist meist nur eine Akzeptanz der Situation, da die Transaktion bereits zurückgesetzt wurde, abzüglich des Zurücksetzens@@TRANCOUNT
auf0
, oder b) es wird ein Fehler angezeigt. Keine gute Wahl, oder?Ein möglicherweise wichtiges Detail dieses Puzzles, das in dieser Dokumentation für nicht ersichtlich
SET XACT_ABORT
ist, ist, dass es diese Sitzungseigenschaft und sogar diesen Beispielcode seit SQL Server 2000 gibt (die Dokumentation ist zwischen den Versionen nahezu identisch), und zwar vor demTRY...CATCH
Konstrukt, das es gab in SQL Server 2005 eingeführt worden ist in dieser Dokumentation noch einmal suchen und Blick auf das Beispiel ( ohne dieTRY...CATCH
) unter VerwendungXACT_ABORT ON
bewirkt eine sofortige Rollback der Transaktion: es gibt keine Transaktionszustand „Commit“ ( man beachte bitte , dass es keine Erwähnung ist alle "nicht festschreibbaren" Transaktionsstatus in dieserSET XACT_ABORT
Dokumentation).Ich denke, es ist vernünftig zu folgern, dass:
TRY...CATCH
Konstrukts in SQL Server 2005 wurde ein neuer Transaktionsstatus (dh "nicht festschreibbar") und dieXACT_STATE()
Funktion zum Abrufen dieser Informationen erforderlich .XACT_STATE()
einesCATCH
Blocks ist nur dann sinnvoll, wenn beide der folgenden Bedingungen erfüllt sind:XACT_ABORT
istOFF
(sonstXACT_STATE()
sollte immer wiederkommen-1
und@@TRANCOUNT
wäre alles was du brauchst)CATCH
Block oder irgendwo in der Kette, wenn die Aufrufe verschachtelt sind, die eine Änderung vornimmt (eineCOMMIT
oder sogar eine beliebige DML-, DDL- usw.-Anweisung), anstatt eine auszuführenROLLBACK
. (Dies ist ein sehr untypischer Anwendungsfall.) ** Bitte beachten Sie den Hinweis unten im Abschnitt UPDATE 3 bezüglich einer inoffiziellen Empfehlung von Microsoft, immerXACT_STATE()
stattdessen zu prüfen@@TRANCOUNT
, und warum Tests zeigen, dass ihre Argumentation nicht aufgeht.TRY...CATCH
Konstrukts in SQL Server 2005 hat dieXACT_ABORT ON
Sitzungseigenschaft größtenteils überholt, da sie ein höheres Maß an Kontrolle über die Transaktion bietet (zumindest haben Sie die Option dazuCOMMIT
, sofernXACT_STATE()
diese nicht zurückgegeben wird-1
).Eine andere Möglichkeit, dies zu betrachten, ist, vor SQL Server 2005 ,
XACT_ABORT ON
eine einfache und zuverlässige Möglichkeit, die Verarbeitung zu beenden, wenn ein Fehler auftrat, im Vergleich zur Überprüfung@@ERROR
nach jeder Anweisung.XACT_STATE()
ist fehlerhaft oder bestenfalls irreführend, da angegeben wird,XACT_STATE() = 1
wann geprüftXACT_ABORT
wirdON
.Der lange Teil ;-)
Ja, dieser Beispielcode auf MSDN ist etwas verwirrend (siehe auch: @@ TRANCOUNT (Rollback) vs. XACT_STATE ) ;-). Und ich halte es für irreführend, weil es entweder etwas zeigt, das keinen Sinn ergibt (aus dem Grund, den Sie fragen: Können Sie überhaupt eine "festschreibbare" Transaktion im
CATCH
Block haben , wenn dies möglichXACT_ABORT
istON
), oder sogar, wenn dies möglich ist konzentriert sich immer noch auf eine technische Möglichkeit, die nur wenige jemals wollen oder brauchen werden, und ignoriert den Grund, warum es wahrscheinlicher ist, dass man sie braucht.Ich denke, es wäre hilfreich, wenn wir sicherstellen würden, dass wir in Bezug auf bestimmte Wörter und Konzepte auf derselben Seite sind:
"schwerwiegender Fehler": Nur um klar zu sein, TRY ... CATCH fängt die meisten Fehler ab. Die Liste der Objekte, die nicht abgefangen werden, befindet sich auf der verknüpften MSDN-Seite im Abschnitt "Fehler, die von einem TRY… CATCH-Konstrukt nicht betroffen sind".
"Wenn ich mich im CATCH befinde, weiß ich, dass eine Transaktion ein Problem hatte" (em phas wird hinzugefügt): Wenn Sie mit "Transaktion" die logische Arbeitseinheit meinen, die Sie durch Gruppieren von Anweisungen in einer expliziten Transaktion bestimmt haben, dann sehr wahrscheinlich ja. Ich denke, die meisten von uns DB-Leuten sind sich einig, dass Rollback "das einzig vernünftige" ist, da wir wahrscheinlich eine ähnliche Ansicht darüber haben, wie und warum wir explizite Transaktionen verwenden und überlegen, welche Schritte eine atomare Einheit bilden sollten der Arbeit.
Aber wenn Sie die tatsächlichen Arbeitseinheiten meinen , die in der expliziten Transaktion gruppiert werden, dann wissen Sie nicht, dass die Transaktion selbst ein Problem hatte. Sie wissen nur, dass eine Anweisung, die innerhalb der explizit definierten Transaktion ausgeführt wird, einen Fehler ausgelöst hat. Möglicherweise handelt es sich jedoch nicht um eine DML- oder DDL-Anweisung. Und selbst wenn es sich um eine DML-Anweisung handelt, kann die Transaktion selbst möglicherweise noch festgeschrieben werden.
In Anbetracht der beiden oben genannten Punkte sollten wir wahrscheinlich zwischen Transaktionen unterscheiden, die Sie nicht festschreiben können, und Transaktionen, die Sie nicht festschreiben möchten.
Wenn
XACT_STATE()
a zurückgegeben1
wird, bedeutet dies, dass die Transaktion "festschreibbar" ist und Sie die Wahl zwischenCOMMIT
oder habenROLLBACK
. Sie möchten es vielleicht nicht festschreiben, aber wenn Sie aus irgendeinem Grund, der schwer zu glätten ist, ein Beispiel haben möchten , könnten Sie es zumindest, weil einige Teile der Transaktion erfolgreich abgeschlossen wurden.Aber wenn a
XACT_STATE()
zurückkommt-1
, müssen Sie das wirklich tun,ROLLBACK
weil ein Teil der Transaktion in einen schlechten Zustand übergegangen ist. Nun stimme ich zu, dass, wenn die Steuerung an den CATCH-Block übergeben wurde, es sinnvoll genug ist, nur zu überprüfen@@TRANCOUNT
, denn selbst wenn Sie die Transaktion festschreiben könnten, warum sollten Sie das wollen?Wenn Sie jedoch am oberen Rand des Beispiels feststellen,
XACT_ABORT ON
ändert sich die Einstellung von ein wenig. Sie können einen regulären Fehler haben, danachBEGIN TRAN
wird die Steuerung an den CATCH-Block übergeben, wennXACT_ABORT
istOFF
und XACT_STATE () wird zurückkehren1
. ABER wenn XACT_ABORT istON
, wird die Transaktion für jeden Fehler "abgebrochen" (dh ungültig gemacht) undXACT_STATE()
kehrt dann zurück-1
. In diesem Fall erscheint es sinnlos,XACT_STATE()
innerhalb desCATCH
Blocks zu prüfen , da er immer ein "-1
WannXACT_ABORT
ist" zurückzugeben scheintON
.Also, wofür ist es dann
XACT_STATE()
? Einige Hinweise sind:Die MSDN-Seite für
TRY...CATCH
im Abschnitt "Nicht festschreibbare Transaktionen und XACT_STATE" lautet:Auf der MSDN-Seite für SET XACT_ABORT im Abschnitt "Hinweise" heißt es:
und:
Auf der MSDN-Seite für BEGIN TRANSACTION im Abschnitt "Hinweise" heißt es:
Die am besten geeignete Verwendung scheint im Kontext von DML-Anweisungen für Verbindungsserver zu liegen. Und ich glaube, ich bin selbst vor Jahren darauf gestoßen. Ich erinnere mich nicht an alle Details, aber es hatte etwas damit zu tun, dass der Remote-Server nicht verfügbar war, und aus irgendeinem Grund wurde dieser Fehler nicht im TRY-Block abgefangen und nie an den CATCH gesendet, und so geschah es ein COMMIT, wenn es nicht hätte sein sollen. Natürlich, das könnte hat ein Problem von nicht gewesen zu
XACT_ABORT
SatzON
anstatt Fehler zu überprüfen ,XACT_STATE()
beide oder möglicherweise. Und ich erinnere mich, etwas gelesen zu haben, das besagt, wenn Sie Verbindungsserver und / oder verteilte Transaktionen verwenden, dann müssen SieXACT_ABORT ON
und / oder verwendenXACT_STATE()
, aber ich kann das Dokument anscheinend jetzt nicht finden. Wenn ich es finde, werde ich dies mit dem Link aktualisieren.Trotzdem habe ich einige Dinge ausprobiert und kann kein Szenario finden, das
XACT_ABORT ON
die Kontrolle über denCATCH
Block mit derXACT_STATE()
Berichterstellung hat und an diesen übergibt1
.Probieren Sie diese Beispiele aus, um die Auswirkung von
XACT_ABORT
auf den Wert von zu sehenXACT_STATE()
:AKTUALISIEREN
Obwohl nicht Teil der ursprünglichen Frage, basierend auf diesen Kommentaren zu dieser Antwort:
Bevor
XACT_ABORT ON
ich es überall verwende, würde ich fragen: Was genau wird hier gewonnen? Ich habe es nicht für notwendig befunden und befürworte generell, dass Sie es nur verwenden sollten, wenn es notwendig ist. Ob SieROLLBACK
mit der in @ Remus ' Antwort gezeigten Vorlage fertig werden möchten oder nicht , oder mit der Vorlage, die ich seit Jahren verwende, ist im Wesentlichen das Gleiche, jedoch ohne den in dieser Antwort gezeigten Speicherpunkt (welcher) behandelt verschachtelte Anrufe):Müssen wir Transaktionen sowohl im C # -Code als auch in gespeicherten Prozeduren abwickeln?
UPDATE 2
Ich habe ein bisschen mehr getestet, diesmal indem ich eine kleine .NET-Konsolen-App erstellt habe, eine Transaktion in der App-Ebene erstellt habe, bevor ich
SqlCommand
Objekte (dh überusing (SqlTransaction _Tran = _Connection.BeginTransaction()) { ...
) ausgeführt habe, und statt einer Anweisung einen Batch-Abbruch-Fehler verwendet habe -aborting Fehler und stellte fest, dass:@@TRANCOUNT
immer noch> 0 ist.COMMIT
Fehlermeldung führt, dass die Transaktion "nicht festschreibbar" ist. Sie können es auch nicht ignorieren / nichts tun, da ein Fehler generiert wird, wenn der Stapel beendet ist und besagt, dass der Stapel mit einer verweilenden, nicht festgeschriebenen Transaktion abgeschlossen wurde und zurückgesetzt wird (also, ähm, wenn er trotzdem automatisch zurückgesetzt wird, warum die Mühe machen, den Fehler zu werfen?). Sie müssen also einen explizitenROLLBACK
Befehl ausgeben , möglicherweise nicht im unmittelbarenCATCH
Block, sondern bevor der Stapel endet.TRY...CATCH
Konstrukt, wennXACT_ABORT
istOFF
, Fehler , die die Transaktion beenden würden, wenn sie automatisch außerhalb eines aufgetretenTRY
Blockes, wie Batch-Abbruch Fehler, wird die Arbeit rückgängig gemacht werden, aber nicht die Tranasction beendet, wird es als „uncommitable“ zu verlassen. Die Ausgabe von aROLLBACK
ist eher eine Formalität, die zum Abschluss der Transaktion erforderlich ist, aber die Arbeit wurde bereits zurückgesetzt.XACT_ABORT
istON
, wirken die meisten Fehler wie ein Batch-Abbruch und verhalten sich daher so, wie es in dem Punkt direkt oben (# 3) beschrieben ist.XACT_STATE()
Zumindest in einemCATCH
Block wird ein-1
zum Batch-Abbrechen von Fehlern angezeigt, wenn zum Zeitpunkt des Fehlers eine Transaktion aktiv war.XACT_STATE()
kehrt manchmal zurück,1
auch wenn keine Transaktion aktiv ist. Wenn@@SPID
(unter anderem) in derSELECT
Liste mit enthalten istXACT_STATE()
,XACT_STATE()
wird 1 zurückgegeben, wenn keine Transaktion aktiv ist. Dieses Verhalten hat in SQL Server 2012 begonnen und ist in 2014 vorhanden, aber ich habe es in 2016 nicht getestet.Unter Berücksichtigung der obigen Punkte:
XACT_STATE()
in demCATCH
Block , wennXACT_ABORT
ist ,ON
da der Wert zurückgegeben wird es immer sein-1
.XACT_STATE()
imCATCH
Block , wennXACT_ABORT
istOFF
mehr sinnvoll ist , da der Wert Rückkehr wird zumindest ein gewisse Variation hat , da es zurück1
zum statement-Abbruch Fehler. Wenn Sie jedoch wie die meisten von uns codieren, ist diese Unterscheidung bedeutungslos, da SieROLLBACK
ohnehin nur deshalb anrufen , weil ein Fehler aufgetreten ist.COMMIT
in demCATCH
Block, dann überprüfen Sie den WertXACT_STATE()
, und sicher sein , zuSET XACT_ABORT OFF;
.XACT_ABORT ON
scheint wenig bis gar keinen Nutzen gegenüber demTRY...CATCH
Konstrukt zu haben.XACT_STATE()
einen bedeutenden Vorteil gegenüber dem bloßen Prüfen bietet@@TRANCOUNT
.XACT_STATE()
kehrt1
in einemCATCH
Block , wennXACT_ABORT
istON
. Ich denke, es ist ein Dokumentationsfehler.XACT_ABORT ON
ist dies ein strittiger Punkt, da ein in einemTRY
Block auftretender Fehler die Änderungen automatisch zurücksetzt.TRY...CATCH
Konstrukt hat den Vorteil,XACT_ABORT ON
dass die gesamte Transaktion nicht automatisch abgebrochen wird und somit die Transaktion (solange sieXACT_STATE()
zurückgegeben wird1
) festgeschrieben werden kann (auch wenn dies ein Edge-Case ist).Beispiel für die
XACT_STATE()
Rückkehr-1
wannXACT_ABORT
istOFF
:UPDATE 3
Bezogen auf Artikel 6 in Abschnitt UPDATE 2 (dh möglicherweise falscher Wert, der zurückgegeben wird,
XACT_STATE()
wenn keine Transaktion aktiv ist):XACT_STATE()
bei Verwendung in Triggern oderINSERT...EXEC
Szenarien keine erwarteten Werte gemeldet : xact_state () kann nicht zuverlässig verwendet werden, um zu bestimmen, ob eine Transaktion zum Scheitern verurteilt ist . In diesen 3 Versionen (die ich nur auf 2008 R2 getestet habe) wirdXACT_STATE()
jedoch nicht fälschlicherweise berichtet,1
wenn sie in einemSELECT
mit verwendet werden@@SPID
.Gegen das hier erwähnte Verhalten ist ein Connect-Fehler aufgetreten, der jedoch als "By Design" geschlossen wird: XACT_STATE () kann in SQL 2012 einen falschen Transaktionsstatus zurückgeben . Der Test wurde jedoch bei der Auswahl eines DMV durchgeführt und es wurde der Schluss gezogen, dass dies natürlich eine systemgenerierte Transaktion hätte, zumindest für einige DMVs. In der abschließenden Antwort der MS wurde außerdem Folgendes festgestellt:
Diese Aussagen sind im folgenden Beispiel falsch:
Daher gibt der neue Connect-Fehler
XACT_STATE () 1 zurück, wenn er in SELECT mit einigen Systemvariablen, jedoch ohne FROM-Klausel verwendet wird
BITTE BEACHTEN SIE, dass in dem direkt darüber verknüpften Connect-Element "XACT_STATE () kann einen falschen Transaktionsstatus in SQL 2012 zurückgeben" Microsoft (nun, ein Vertreter von) Folgendes angibt:
Ich kann jedoch keinen Grund finden, nicht zu vertrauen
@@TRANCOUNT
. Der folgende Test zeigt, dass in einer Auto-Commit-Transaktion@@TRANCOUNT
tatsächlich Folgendes zurückgegeben1
wird:Ich habe auch an einem realen Tisch mit einem Trigger getestet und
@@TRANCOUNT
im Trigger genau berichtet1
, obwohl keine explizite Transaktion gestartet wurde.quelle
Defensive Programmierung erfordert, dass Sie Code schreiben, der so viele bekannte Zustände wie möglich verarbeitet, wodurch die Möglichkeit von Fehlern verringert wird.
Das Überprüfen von XACT_STATE (), um festzustellen, ob ein Rollback ausgeführt werden kann, ist einfach eine gute Vorgehensweise. Wenn Sie blind einen Rollback versuchen, können Sie versehentlich einen Fehler in Ihrem TRY ... CATCH verursachen.
Eine Möglichkeit, wie ein Rollback in einem TRY ... CATCH fehlschlagen kann, besteht darin, dass Sie eine Transaktion nicht explizit gestartet haben. Das Kopieren und Einfügen von Codeblöcken kann dies leicht verursachen.
quelle
ROLLBACK
nicht funktionieren würdeCATCH
und Sie gaben ein gutes Beispiel. Ich denke, es kann auch schnell chaotisch werden, wenn geschachtelte Transaktionen und geschachtelte gespeicherte Prozeduren mit eigenenTRY ... CATCH ... ROLLBACK
beteiligt sind.IF (XACT_STATE()) = 1 COMMIT TRANSACTION;
Wie können wirCATCH
mit einer festschreibbaren Transaktion im Block landen ? Ich würde es nicht wagen, irgendeinen (möglichen) Müll aus dem Haus zu werfenCATCH
. Meine Argumentation ist: Wenn wir uns im Inneren befinden, istCATCH
etwas schief gelaufen, ich kann dem Status der Datenbank nicht vertrauen, also ist es besser,ROLLBACK
was immer wir haben.