In welchen Fällen kann eine Transaktion innerhalb des CATCH-Blocks festgeschrieben werden, wenn XACT_ABORT auf ON gesetzt ist?

13

Ich habe MSDN über TRY...CATCHund gelesen XACT_STATE.

Das folgende Beispiel bestimmt XACT_STATEim CATCHBlock eines TRY…CATCHKonstrukts, 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_STATEzurückkommt?

Bitte beachten Sie, dass im Beispiel das Flag gesetzt XACT_ABORTist ON.

Wenn im TRYBlock 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, CATCHund 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 CATCHmit einer Transaktion übergeben werden kann, die festgeschrieben werden kann, wenn auf festgelegt XACT_ABORTistON .

Der MSDN-Artikel über SET XACT_ABORThat ein Beispiel, in dem einige Anweisungen in einer Transaktion erfolgreich ausgeführt werden und andere fehlschlagen, wenn auf XACT_ABORTfestgelegt OFFist. Ich verstehe das. Aber SET XACT_ABORT ONwie kann es passieren, dass XACT_STATE()1 innerhalb des CATCHBlocks 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 ONdem CATCHBlock 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
Vladimir Baranov
quelle

Antworten:

8

Es stellt sich heraus, dass die Transaktion nicht innerhalb des CATCHBlocks festgeschrieben werden kann , wenn auf gesetzt XACT_ABORTist ON.

Das Beispiel von MSDN ist etwas irreführend, da die Prüfung impliziert, dass XACT_STATEin einigen Fällen 1 zurückgegeben werden kann und COMMITdie Transaktion möglicherweise möglich ist.

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

Es ist nicht wahr, XACT_STATEwird niemals 1 innerhalb des CATCHBlocks zurückgeben, wenn auf gesetzt XACT_ABORTist ON.

Anscheinend sollte der MSDN-Beispielcode in erster Linie die Verwendung der XACT_STATE()Funktion unabhängig von der XACT_ABORTEinstellung veranschaulichen . Der Beispielcode sieht generisch genug aus, um mit XACT_ABORTset to ONund zu arbeiten OFF. Es ist nur so, dass mit XACT_ABORT = ONder Überprüfung IF (XACT_STATE()) = 1unnötig wird.


Es gibt eine sehr gute Sammlung detaillierter Artikel über die Fehler- und Transaktionsbehandlung in SQL Server von Erland Sommarskog. In Teil 2 - Klassifizierung von Fehlern präsentiert er eine umfassende Tabelle, in der alle Fehlerklassen zusammengefasst sind, wie sie von SQL Server behandelt werden und wie TRY ... CATCHund wie XACT_ABORTsich das Verhalten ändert.

+-----------------------------+---------------------------++------------------------------+
|                             |     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    |
+-----------------------------+-------+-------+-----+-----++------------------+-----+-----+

Die letzte Spalte in der Tabelle beantwortet die Frage. Mit TRY-CATCHund mit XACT_ABORT ONder Transaktion ist in allen möglichen Fällen zum Scheitern verurteilt.

Eine Anmerkung, die nicht in den Rahmen der Frage fällt. Wie Erland sagt, ist diese Konsistenz einer der Gründe , um Satz XACT_ABORTzu ON:

Ich habe bereits die Empfehlung gegeben, dass Ihre gespeicherten Prozeduren den Befehl enthalten sollten SET XACT_ABORT, NOCOUNT ON. Wenn Sie sich die Tabelle oben ansehen, sehen Sie, dass XACT_ABORTtatsächlich ein höheres Maß an Konsistenz vorliegt. Zum Beispiel ist die Transaktion immer zum Scheitern verurteilt. Im Folgenden werde ich viele Beispiele zeigen, auf die ich gesetzt XACT_ABORThabe OFF, damit Sie verstehen, warum Sie diese Standardeinstellung vermeiden sollten.

Vladimir Baranov
quelle
7

Ich würde das anders angehen. XACT_ABORT_ONIst ein Vorschlaghammer, können Sie einen verfeinerten Ansatz verwenden, siehe Ausnahmebehandlung und verschachtelte Transaktionen :

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

Bei diesem Ansatz wird nach Möglichkeit nur die im TRY-Block ausgeführte Arbeit zurückgesetzt und der Status auf den Status vor dem Aufrufen des TRY-Blocks zurückgesetzt. Auf diese Weise können Sie komplexe Vorgänge wie das Wiederholen eines Cursors ausführen, ohne im Fehlerfall die gesamte Arbeit zu verlieren. Der einzige Nachteil besteht darin, dass Sie durch die Verwendung von Transaktionssicherungspunkten nicht in der Lage sind, Elemente zu verwenden, die mit Sicherungspunkten nicht kompatibel sind, wie beispielsweise verteilte Transaktionen.

Remus Rusanu
quelle
Vielen Dank für Ihre Antwort, aber die Frage ist nicht wirklich , ob wir setzen sollten XACT_ABORTauf ONoder OFF.
Vladimir Baranov
7

TL; DR / Zusammenfassung: Zu diesem Teil der Frage:

Ich sehe nicht, in welchen Fällen das Steuerelement CATCHmit einer Transaktion übergeben werden kann, die festgeschrieben werden kann, wenn auf festgelegt XACT_ABORTistON .

Ich habe jetzt auf diese ziemlich viel Prüfung getan und ich kann keine Fälle , wo XACT_STATE()Renditen 1innerhalb eines CATCHBlocks , wenn @@TRANCOUNT > 0 und der Sitzungs Eigenschaft XACT_ABORTist ON. Und tatsächlich laut der aktuellen MSDN-Seite für SET XACT_ABORT :

Wenn SET XACT_ABORT auf ON gesetzt ist und eine Transact-SQL-Anweisung einen Laufzeitfehler auslöst, wird die gesamte Transaktion beendet und ein Rollback ausgeführt.

Diese Aussage scheint mit Ihrer Spekulation und meinen Ergebnissen übereinzustimmen.

Der MSDN-Artikel über SET XACT_ABORTenthält ein Beispiel, in dem einige Anweisungen in einer Transaktion erfolgreich ausgeführt werden und andere fehlschlagen, wenn auf festgelegt XACT_ABORTistOFF

Richtig, aber die Anweisungen in diesem Beispiel befinden sich nicht in einem TRYBlock. Die gleichen Aussagen innerhalb eines TRYBlocks würde verhindern , dass nach wie vor der Ausführung für Aussagen nach der, die den Fehler verursacht hat , aber davon aus, dass XACT_ABORTist OFF, wenn die Steuerung an die übergeben wird CATCHBlock 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, wenn XACT_ABORTist ONdann werden alle Änderungen automatisch vor Rollback, und dann haben Sie die Wahl, entweder gegeben: a) Ausgabe einerROLLBACKDies ist meist nur eine Akzeptanz der Situation, da die Transaktion bereits zurückgesetzt wurde, abzüglich des Zurücksetzens @@TRANCOUNTauf 0, 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_ABORTist, 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 dem TRY...CATCHKonstrukt, das es gab in SQL Server 2005 eingeführt worden ist in dieser Dokumentation noch einmal suchen und Blick auf das Beispiel ( ohne die TRY...CATCH) unter Verwendung XACT_ABORT ONbewirkt eine sofortige Rollback der Transaktion: es gibt keine Transaktionszustand „Commit“ ( man beachte bitte , dass es keine Erwähnung ist alle "nicht festschreibbaren" Transaktionsstatus in dieser SET XACT_ABORTDokumentation).

Ich denke, es ist vernünftig zu folgern, dass:

  1. Durch die Einführung des TRY...CATCHKonstrukts in SQL Server 2005 wurde ein neuer Transaktionsstatus (dh "nicht festschreibbar") und die XACT_STATE()Funktion zum Abrufen dieser Informationen erforderlich .
  2. Das Einchecken XACT_STATE()eines CATCHBlocks ist nur dann sinnvoll, wenn beide der folgenden Bedingungen erfüllt sind:
    1. XACT_ABORTist OFF(sonst XACT_STATE()sollte immer wiederkommen -1und @@TRANCOUNTwäre alles was du brauchst)
    2. Sie haben Logik im CATCHBlock oder irgendwo in der Kette, wenn die Aufrufe verschachtelt sind, die eine Änderung vornimmt (eine COMMIToder sogar eine beliebige DML-, DDL- usw.-Anweisung), anstatt eine auszuführen ROLLBACK. (Dies ist ein sehr untypischer Anwendungsfall.) ** Bitte beachten Sie den Hinweis unten im Abschnitt UPDATE 3 bezüglich einer inoffiziellen Empfehlung von Microsoft, immer XACT_STATE()stattdessen zu prüfen @@TRANCOUNT, und warum Tests zeigen, dass ihre Argumentation nicht aufgeht.
  3. Die Einführung des TRY...CATCHKonstrukts in SQL Server 2005 hat die XACT_ABORT ONSitzungseigenschaft größtenteils überholt, da sie ein höheres Maß an Kontrolle über die Transaktion bietet (zumindest haben Sie die Option dazu COMMIT, sofern XACT_STATE()diese nicht zurückgegeben wird -1).
    Eine andere Möglichkeit, dies zu betrachten, ist, vor SQL Server 2005 , XACT_ABORT ONeine einfache und zuverlässige Möglichkeit, die Verarbeitung zu beenden, wenn ein Fehler auftrat, im Vergleich zur Überprüfung @@ERRORnach jeder Anweisung.
  4. Der Dokumentations-Beispielcode für XACT_STATE()ist fehlerhaft oder bestenfalls irreführend, da angegeben wird, XACT_STATE() = 1wann geprüft XACT_ABORTwird ON.

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 CATCHBlock haben , wenn dies möglich XACT_ABORTist ON), 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.

Wenn im TRY-Block ein schwerwiegender Fehler vorliegt, wird die Steuerung an CATCH übergeben. Wenn ich mich also im CATCH befinde, weiß ich, dass bei einer Transaktion ein Problem aufgetreten ist und dass in diesem Fall nur ein Rollback sinnvoll ist, nicht wahr?

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ückgegeben 1wird, bedeutet dies, dass die Transaktion "festschreibbar" ist und Sie die Wahl zwischen COMMIToder haben ROLLBACK. 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, ROLLBACKweil 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, danach BEGIN TRANwird die Steuerung an den CATCH-Block übergeben, wenn XACT_ABORTist OFFund XACT_STATE () wird zurückkehren 1. ABER wenn XACT_ABORT ist ON, wird die Transaktion für jeden Fehler "abgebrochen" (dh ungültig gemacht) und XACT_STATE()kehrt dann zurück -1. In diesem Fall erscheint es sinnlos, XACT_STATE()innerhalb des CATCHBlocks zu prüfen , da er immer ein " -1Wann XACT_ABORTist" zurückzugeben scheint ON.

Also, wofür ist es dann XACT_STATE()? Einige Hinweise sind:

  • Die MSDN-Seite für TRY...CATCHim Abschnitt "Nicht festschreibbare Transaktionen und XACT_STATE" lautet:

    Ein Fehler, der normalerweise eine Transaktion außerhalb eines TRY-Blocks beendet, führt dazu, dass eine Transaktion in einen nicht festschreibbaren Zustand wechselt, wenn der Fehler innerhalb eines TRY-Blocks auftritt.

  • Auf der MSDN-Seite für SET XACT_ABORT im Abschnitt "Hinweise" heißt es:

    Wenn SET XACT_ABORT auf OFF gesetzt ist, wird in einigen Fällen nur die Transact-SQL-Anweisung zurückgesetzt, die den Fehler ausgelöst hat, und die Transaktion wird fortgesetzt.

    und:

    XACT_ABORT muss für Datenänderungsanweisungen in einer impliziten oder expliziten Transaktion für die meisten OLE DB-Anbieter, einschließlich SQL Server, auf ON gesetzt werden.

  • Auf der MSDN-Seite für BEGIN TRANSACTION im Abschnitt "Hinweise" heißt es:

    Die von der BEGIN TRANSACTION-Anweisung gestartete lokale Transaktion wird in eine verteilte Transaktion eskaliert, wenn die folgenden Aktionen ausgeführt werden, bevor die Anweisung festgeschrieben oder zurückgesetzt wird:

    • Eine INSERT-, DELETE- oder UPDATE-Anweisung, die auf eine Remotetabelle auf einem Verbindungsserver verweist, wird ausgeführt. Die INSERT-, UPDATE- oder DELETE-Anweisung schlägt fehl, wenn der OLE DB-Anbieter, der für den Zugriff auf den Verbindungsserver verwendet wird, die ITransactionJoin-Schnittstelle nicht unterstützt.

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_ABORTSatz ONanstatt 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 Sie XACT_ABORT ONund / oder verwenden XACT_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 ONdie Kontrolle über den CATCHBlock mit der XACT_STATE()Berichterstellung hat und an diesen übergibt 1.

Probieren Sie diese Beispiele aus, um die Auswirkung von XACT_ABORTauf den Wert von zu sehen 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;

AKTUALISIEREN

Obwohl nicht Teil der ursprünglichen Frage, basierend auf diesen Kommentaren zu dieser Antwort:

Ich habe durch Erland Erzeugnisse auf Lesefehler und Transaktionsabwicklung , wo er sagt , dass XACT_ABORTist OFFstandardmäßig für Legacy - Gründe und normalerweise sollten wir es einstellen ON.
...
"... wenn Sie der Empfehlung folgen und SET XACT_ABORT ON ausführen, ist die Transaktion immer zum Scheitern verurteilt."

Bevor XACT_ABORT ONich 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 Sie ROLLBACKmit 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 SqlCommandObjekte (dh über using (SqlTransaction _Tran = _Connection.BeginTransaction()) { ...) ausgeführt habe, und statt einer Anweisung einen Batch-Abbruch-Fehler verwendet habe -aborting Fehler und stellte fest, dass:

  1. Eine "unverbindliche" Transaktion ist eine Transaktion, die größtenteils bereits zurückgesetzt wurde (die Änderungen wurden rückgängig gemacht), aber @@TRANCOUNTimmer noch> 0 ist.
  2. Wenn Sie eine "nicht festschreibbare" Transaktion haben, können Sie keine ausstellen, da dies zu einer COMMITFehlermeldung 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 expliziten ROLLBACKBefehl ausgeben , möglicherweise nicht im unmittelbaren CATCHBlock, sondern bevor der Stapel endet.
  3. In einem TRY...CATCHKonstrukt, wenn XACT_ABORTist OFF, Fehler , die die Transaktion beenden würden, wenn sie automatisch außerhalb eines aufgetreten TRYBlockes, 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 a ROLLBACKist eher eine Formalität, die zum Abschluss der Transaktion erforderlich ist, aber die Arbeit wurde bereits zurückgesetzt.
  4. Wann XACT_ABORTist ON, wirken die meisten Fehler wie ein Batch-Abbruch und verhalten sich daher so, wie es in dem Punkt direkt oben (# 3) beschrieben ist.
  5. XACT_STATE()Zumindest in einem CATCHBlock wird ein -1zum Batch-Abbrechen von Fehlern angezeigt, wenn zum Zeitpunkt des Fehlers eine Transaktion aktiv war.
  6. XACT_STATE()kehrt manchmal zurück, 1auch wenn keine Transaktion aktiv ist. Wenn @@SPID(unter anderem) in der SELECTListe mit enthalten ist XACT_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:

  • Gegeben Punkte # 4 und # 5, da die meisten (oder alle?) Fehler eine Transaktion „uncommitable“ machen wird, so scheint es völlig sinnlos , zu überprüfen , XACT_STATE()in dem CATCHBlock , wenn XACT_ABORTist , ONda der Wert zurückgegeben wird es immer sein -1.
  • Überprüfung XACT_STATE()im CATCHBlock , wenn XACT_ABORTist OFFmehr sinnvoll ist , da der Wert Rückkehr wird zumindest ein gewisse Variation hat , da es zurück 1zum statement-Abbruch Fehler. Wenn Sie jedoch wie die meisten von uns codieren, ist diese Unterscheidung bedeutungslos, da Sie ROLLBACKohnehin nur deshalb anrufen , weil ein Fehler aufgetreten ist.
  • Wenn Sie eine Situation , den Haftbefehl ausstellt , handelt eine COMMITin dem CATCHBlock, dann überprüfen Sie den Wert XACT_STATE(), und sicher sein , zu SET XACT_ABORT OFF;.
  • XACT_ABORT ONscheint wenig bis gar keinen Nutzen gegenüber dem TRY...CATCHKonstrukt zu haben.
  • Ich kann kein Szenario finden, in dem das Prüfen XACT_STATE()einen bedeutenden Vorteil gegenüber dem bloßen Prüfen bietet @@TRANCOUNT.
  • Ich kann auch kein Szenario , wo finden XACT_STATE()kehrt 1in einem CATCHBlock , wenn XACT_ABORTist ON. Ich denke, es ist ein Dokumentationsfehler.
  • Ja, Sie können eine Transaktion, die Sie nicht explizit begonnen haben, rückgängig machen. Und im Zusammenhang mit der Verwendung XACT_ABORT ONist dies ein strittiger Punkt, da ein in einem TRYBlock auftretender Fehler die Änderungen automatisch zurücksetzt.
  • Das TRY...CATCHKonstrukt hat den Vorteil, XACT_ABORT ONdass die gesamte Transaktion nicht automatisch abgebrochen wird und somit die Transaktion (solange sie XACT_STATE()zurückgegeben wird 1) festgeschrieben werden kann (auch wenn dies ein Edge-Case ist).

Beispiel für die XACT_STATE()Rückkehr -1wann XACT_ABORTist 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;

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):

  • Das seltsame / fehlerhafte Verhalten wurde in SQL Server 2012 gestartet (bisher mit 2012 SP2 und 2014 SP1 getestet)
  • In den SQL Server-Versionen 2005, 2008 und 2008 R2 wurden XACT_STATE()bei Verwendung in Triggern oder INSERT...EXECSzenarien 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) wird XACT_STATE()jedoch nicht fälschlicherweise berichtet, 1wenn sie in einem SELECTmit 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:

    Beachten Sie, dass eine IF-Anweisung und auch ein SELECT ohne FROM keine Transaktion starten.
    Wenn Sie beispielsweise SELECT XACT_STATE () ausführen, wenn Sie keine zuvor vorhandene Transaktion haben, wird 0 zurückgegeben.

    Diese Aussagen sind im folgenden Beispiel falsch:

    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
  • 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:

@@ trancount gibt die Anzahl der BEGIN TRAN-Anweisungen zurück. Es ist daher kein verlässlicher Indikator dafür, ob eine Transaktion aktiv ist. XACT_STATE () gibt auch 1 zurück, wenn eine Autocommit-Transaktion aktiv ist, und ist daher ein zuverlässigerer Indikator dafür, ob eine Transaktion aktiv ist.

Ich kann jedoch keinen Grund finden, nicht zu vertrauen @@TRANCOUNT. Der folgende Test zeigt, dass in einer Auto-Commit-Transaktion @@TRANCOUNTtatsächlich Folgendes zurückgegeben 1wird:

--- 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

Ich habe auch an einem realen Tisch mit einem Trigger getestet und @@TRANCOUNTim Trigger genau berichtet 1, obwohl keine explizite Transaktion gestartet wurde.

Solomon Rutzky
quelle
4

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.

Max Vernon
quelle
Danke für deine Antwort. Ich konnte mir einfach keinen Fall vorstellen, in dem einfach ROLLBACKnicht funktionieren würde CATCHund Sie gaben ein gutes Beispiel. Ich denke, es kann auch schnell chaotisch werden, wenn geschachtelte Transaktionen und geschachtelte gespeicherte Prozeduren mit eigenen TRY ... CATCH ... ROLLBACKbeteiligt sind.
Vladimir Baranov
Dennoch würde ich es begrüßen, wenn Sie Ihre Antwort bezüglich des zweiten Teils erweitern könnten - IF (XACT_STATE()) = 1 COMMIT TRANSACTION; Wie können wir CATCHmit einer festschreibbaren Transaktion im Block landen ? Ich würde es nicht wagen, irgendeinen (möglichen) Müll aus dem Haus zu werfen CATCH. Meine Argumentation ist: Wenn wir uns im Inneren befinden, ist CATCHetwas schief gelaufen, ich kann dem Status der Datenbank nicht vertrauen, also ist es besser, ROLLBACKwas immer wir haben.
Vladimir Baranov