Rollback, wenn 3 gespeicherte Prozeduren von einer gespeicherten Prozedur gestartet werden

23

Ich habe eine gespeicherte Prozedur, die nur 3 gespeicherte Prozeduren in ihnen ausführt. Ich verwende nur 1 Parameter zum Speichern, wenn der Master-SP erfolgreich ist.

Wenn die erste gespeicherte Prozedur in der gespeicherten Hauptprozedur einwandfrei funktioniert, die zweite gespeicherte Prozedur jedoch fehlschlägt, werden dann automatisch alle SPs in der Hauptprozedur zurückgesetzt, oder muss ich einen Befehl ausführen?

Hier ist meine Vorgehensweise:

CREATE PROCEDURE [dbo].[spSavesomename] 
    -- Add the parameters for the stored procedure here

    @successful bit = null output
AS
BEGIN
begin transaction createSavebillinginvoice
    begin Try
    -- SET NOCOUNT ON added to prevent extra result sets from
    -- interfering with SELECT statements.
    SET NOCOUNT ON;

   BEGIN 

   EXEC [dbo].[spNewBilling1]

   END

   BEGIN 

   EXEC [dbo].[spNewBilling2]

   END

   BEGIN 

   EXEC [dbo].[spNewBilling3]

   END 

   set @successful  = 1

   end Try

    begin Catch
        rollback transaction createSavesomename
        insert into dbo.tblErrorMessage(spName, errorMessage, systemDate) 
             values ('spSavesomename', ERROR_MESSAGE(), getdate())

        return
    end Catch
commit transaction createSavesomename
return
END

GO
user2483342
quelle
Wenn spNewBilling3ein Fehler ausgelöst wird, Sie aber nicht zurücksetzen spNewBilling2oder möchten spNewBilling1, dann entfernen Sie einfach [begin|rollback|commit] transaction createSavebillinginvoiceaus spSavesomename.
Mike

Antworten:

56

Wenn nur der in der Frage angegebene Code verwendet wird und angenommen wird, dass keiner der drei Unterprozesse eine explizite Transaktionsbehandlung aufweist, wird ein Fehler in einem der drei Unterprozesse abgefangen, und der ROLLBACKim CATCHBlock enthaltene Code wird alle zurücksetzen der Arbeit.

ABER hier sind einige Dinge zu beachten (zumindest in SQL Server):

  • Es gibt immer nur eine echte Transaktion (die erste), egal wie oft Sie anrufenBEGIN TRAN

    • Sie können eine Transaktion benennen (wie Sie es hier getan haben) und dieser Name wird in den Protokollen angezeigt, aber die Benennung hat nur Bedeutung für die erste / äußerste Transaktion (da die erste Transaktion wiederum die Transaktion ist).
    • Bei jedem Aufruf BEGIN TRANwird der Transaktionszähler um 1 erhöht, unabhängig davon, ob er benannt wurde oder nicht.
    • Sie können das aktuelle Niveau sehen, indem Sie tun SELECT @@TRANCOUNT;
    • Alle COMMITBefehle, die @@TRANCOUNTbei 2 oder höher ausgegeben werden, reduzieren den Transaktionszähler nacheinander.
    • Nichts wird jemals festgeschrieben, bis ein COMMITausgestellt wird, wenn das um @@TRANCOUNTist1
    • Nur für den Fall, dass die obigen Informationen nicht eindeutig sind: Unabhängig von der Transaktionsstufe gibt es keine tatsächliche Verschachtelung von Transaktionen.
  • Durch das Speichern von Punkten können Sie eine Teilmenge der Arbeit innerhalb der Transaktion erstellen, die rückgängig gemacht werden kann.

    • Speicherpunkte werden über den SAVE TRAN {save_point_name}Befehl angelegt / markiert
    • Speicherpunkte markieren den Beginn der Teilmenge der Arbeit, die rückgängig gemacht werden kann, ohne die gesamte Transaktion rückgängig zu machen.
    • Speicherpunktnamen müssen nicht eindeutig sein, aber wenn Sie denselben Namen mehrmals verwenden, werden immer noch unterschiedliche Speicherpunkte erstellt.
    • Speicherpunkte können verschachtelt werden.
    • Speicherpunkte können nicht festgeschrieben werden.
    • Punkte speichern kann über rückgängig gemacht werden ROLLBACK {save_point_name}. (mehr dazu weiter unten)
    • Durch das Zurücksetzen eines Speicherpunkts werden alle nach dem letzten Aufruf von aufgetretenen Vorgänge rückgängig gemacht SAVE TRAN {save_point_name}, einschließlich aller Speicherpunkte, die erstellt wurden, nachdem der zurückgesetzte erstellt wurde (daher die "Verschachtelung").
    • Das Zurücksetzen eines Speicherpunkts hat keine Auswirkungen auf die Transaktionsanzahl / -stufe
    • Vor der ersten Transaktion ausgeführte Arbeiten SAVE TRANkönnen nur rückgängig gemacht werden, indem ROLLBACKdie gesamte Transaktion vollständig ausgeführt wird.
    • Um es klar auszudrücken: Das Ausgeben eines COMMITZeitpunkts @@TRANCOUNTvon 2 oder höher hat keine Auswirkung auf das Speichern von Punkten (da es auch außerhalb dieses Zählers keine Transaktionsstufen über 1 gibt).
  • Sie können keine bestimmten benannten Transaktionen festschreiben. Der "Transaktionsname" COMMITwird ignoriert und dient nur der Lesbarkeit.

  • Bei einer ROLLBACKAusgabe ohne Namen werden immer ALLE Transaktionen zurückgesetzt.

  • Ein ROLLBACKausgestelltes mit einem Namen muss entweder entsprechen:

    • Die erste Transaktion unter der Annahme, dass sie benannt wurde:
      Unter der Annahme, dass keine SAVE TRANmit demselben Transaktionsnamen aufgerufen wurde, werden ALLE Transaktionen zurückgesetzt.
    • Ein "Speicherpunkt" (oben beschrieben):
      Dieses Verhalten macht alle Änderungen "rückgängig", die seit dem letzten SAVE TRAN {save_point_name} Aufruf vorgenommen wurden.
    • Wenn die erste Transaktion a) benannt wurde und b) SAVE TRANBefehle mit ihrem Namen ausgegeben wurden, macht jeder ROLLBACK dieses Transaktionsnamens jeden Speicherpunkt rückgängig, bis keiner mehr von diesem Namen übrig ist. Danach wird durch ein ROLLBACK mit diesem Namen ALLE Transaktionen zurückgesetzt.
    • Angenommen, die folgenden Befehle wurden in der angegebenen Reihenfolge ausgeführt:

      BEGIN TRAN A -- @@TRANCOUNT is now 1
      -- DML Query 1
      SAVE TRAN A
      -- DML Query 2
      SAVE TRAN A
      -- DML Query 3
      
      BEGIN TRAN B -- @@TRANCOUNT is now 2
      SAVE TRAN B
      -- DML Query 4

      Nun, wenn Sie Probleme haben (jedes der folgenden Szenarien ist unabhängig voneinander):

      • ROLLBACK TRAN Beinmal: "DML Query 4" wird rückgängig gemacht. @@TRANCOUNTist noch 2.
      • ROLLBACK TRAN Bzweimal: "DML-Abfrage 4" wird rückgängig gemacht, und dann tritt ein Fehler auf, da für "B" kein entsprechender Speicherpunkt vorhanden ist. @@TRANCOUNTist noch 2.
      • ROLLBACK TRAN Aeinmal: "DML-Abfrage 4" und "DML-Abfrage 3" werden rückgängig gemacht. @@TRANCOUNTist noch 2.
      • ROLLBACK TRAN Azweimal: "DML-Abfrage 4", "DML-Abfrage 3" und "DML-Abfrage 2" werden rückgängig gemacht. @@TRANCOUNTist noch 2.
      • ROLLBACK TRAN ADreimal: "DML Query 4", "DML Query 3" und "DML Query 2" werden rückgängig gemacht. Dann wird die gesamte Transaktion rückgängig gemacht (alles, was übrig blieb, war "DML-Abfrage 1"). @@TRANCOUNTist jetzt 0.
      • COMMITeinmal: @@TRANCOUNTgeht runter auf 1.
      • COMMITeinmal und dann ROLLBACK TRAN Beinmal: @@TRANCOUNTgeht zu 1. Dann wird "DML Query 4" rückgängig gemacht (was beweist, dass COMMIT nichts getan hat). @@TRANCOUNTist noch 1.
  • Transaktionsnamen und Speicherpunktnamen:

    • kann bis zu 32 Zeichen haben
    • unabhängig von den Kollatierungen auf Instanzebene oder Datenbankebene als binär kollatiert behandelt werden (wobei die Groß- und Kleinschreibung in der aktuellen Dokumentation nicht berücksichtigt wird).
    • Weitere Informationen finden Sie im Abschnitt Transaktionsnamen des folgenden Beitrags: Was enthält ein Name ?: In der verrückten Welt der T-SQL-Bezeichner
  • Eine gespeicherte Prozedur ist an sich keine implizite Transaktion. Jede Abfrage, wenn keine explizite Transaktion gestartet wurde, ist eine implizite Transaktion. Aus diesem Grund sind explizite Transaktionen für einzelne Abfragen nicht erforderlich, es sei denn, es gibt einen programmgesteuerten Grund für die ROLLBACKAusführung von a. Andernfalls führt ein Fehler in der Abfrage zu einem automatischen Rollback dieser Abfrage.

  • Wenn eine gespeicherte Prozedur @@TRANCOUNTaufgerufen wird, muss sie mit dem Wert beendet werden , der dem Wert entspricht , mit dem sie aufgerufen wurde. Das heißt, Sie können nicht:

    • Starten Sie eine BEGIN TRANin der Prozedur, ohne sie festzuschreiben, und erwarten Sie, dass sie in der aufrufenden / übergeordneten Prozedur festgeschrieben wird.
    • Sie können a nicht ausgeben, ROLLBACKwenn eine explizite Transaktion vor dem Aufruf des Procs gestartet wurde, da diese @@TRANCOUNTauf 0 zurückgesetzt wird.

    Wenn Sie eine gespeicherte Prozedur mit einer Transaktionsanzahl beenden, die höher oder niedriger ist als beim Starren, wird eine Fehlermeldung angezeigt, die der folgenden ähnelt:

    Meldung 266, Ebene 16, Status 2, Prozedur YourProcName, Zeile 0 Die
    Transaktionsanzahl nach EXECUTE gibt eine nicht übereinstimmende Anzahl von BEGIN- und COMMIT-Anweisungen an. Vorherige Zählung = X, aktuelle Zählung = Y.

  • Tabellenvariablen sind wie reguläre Variablen nicht an Transaktionen gebunden.


In Bezug auf die Transaktionsbehandlung in Prozessen, die entweder unabhängig aufgerufen werden können (und daher eine Transaktionsbehandlung benötigen) oder von anderen Prozessen aufgerufen werden können (und daher keine Transaktionsbehandlung benötigen): Dies kann auf verschiedene Arten erfolgen.

Die Art und Weise , dass ich es seit einigen Jahren wurde der Handhabung gut zu funktionieren scheint , ist nur BEGIN/ COMMIT/ ROLLBACKauf der äußersten Schicht. Sub-Proc-Aufrufe überspringen einfach die Transaktionsbefehle. Ich habe unten beschrieben, was ich in jeden Proc stecke (nun, jeder, der eine Transaktionsabwicklung benötigt).

  • An der Spitze jedes proc, DECLARE @InNestedTransaction BIT;
  • Anstelle von einfach BEGIN TRAN, mache:

    IF (@@TRANCOUNT = 0)
    BEGIN
       SET @InNestedTransaction = 0;
       BEGIN TRAN; -- only start a transaction if not already in one
    END;
    ELSE
    BEGIN
       SET @InNestedTransaction = 1;
    END;
  • Anstelle von einfach COMMIT, mache:

    IF (@@TRANCOUNT > 0 AND @InNestedTransaction = 0)
    BEGIN
       COMMIT;
    END;
  • Anstelle von einfach ROLLBACK, mache:

    IF (@@TRANCOUNT > 0 AND @InNestedTransaction = 0)
    BEGIN
       ROLLBACK;
    END;

Diese Methode sollte unabhängig davon, ob die Transaktion in SQL Server oder auf App-Ebene gestartet wurde, gleich funktionieren.

Für die vollständige Vorlage dieser Transaktion im Umgang mit TRY...CATCHKonstrukts, bitte meine Antwort auf die folgende Frage DBA.SE sehen: Müssen wir Transaktion in C # -Code sowie in Stored Procedure zu handhaben .


Abgesehen von den "Grundlagen" gibt es einige zusätzliche Nuancen von Transaktionen, die zu beachten sind:

  • Standardmäßig werden Transaktionen die meiste Zeit nicht automatisch zurückgesetzt / abgebrochen, wenn ein Fehler auftritt. Dies ist normalerweise kein Problem, solange Sie über eine ordnungsgemäße Fehlerbehandlung verfügen und sich ROLLBACKselbst anrufen . Manchmal werden die Dinge jedoch kompliziert, z. B. bei Batch-Abbruch-Fehlern oder bei der Verwendung OPENQUERY(oder bei Verbindungsservern im Allgemeinen), und auf dem fernen System tritt ein Fehler auf. Während die meisten Fehler mit abgefangen werden können TRY...CATCH, gibt es zwei, die nicht auf diese Weise abgefangen werden können (ich kann mich jedoch nicht an die erinnern, die gerade vorliegen - Nachforschungen anstellen). In diesen Fällen müssen Sie verwenden, SET XACT_ABORT ONum die Transaktion ordnungsgemäß zurückzusetzen.

    SET XACT_ABORT ON bewirkt, dass SQL Server eine Transaktion (falls eine aktiv ist) sofort zurücksetzt und den Stapel abbricht, wenn ein Fehler auftritt. Diese Einstellung war vor SQL Server 2005 vorhanden, mit dem das TRY...CATCHKonstrukt eingeführt wurde. Meistens TRY...CATCHbewältigt es die meisten Situationen und überflüssig daher die Notwendigkeit für XACT_ABORT ON. Wenn Sie jedoch OPENQUERY(und möglicherweise ein anderes Szenario, an das ich mich momentan nicht erinnere) verwenden, müssen Sie es dennoch verwenden SET XACT_ABORT ON;.

  • Innerhalb eines Triggers XACT_ABORTwird implizit auf gesetzt ON. Dies führt dazu, dass ein Fehler im Trigger die gesamte DML-Anweisung abbricht, die den Trigger ausgelöst hat.

  • Sie sollten immer über eine ordnungsgemäße Fehlerbehandlung verfügen, insbesondere bei der Verwendung von Transaktionen. Das TRY...CATCHin SQL Server 2005 eingeführte Konstrukt bietet eine Möglichkeit, mit fast allen Situationen umzugehen. Es ist eine willkommene Verbesserung gegenüber dem Testen @@ERRORnach jeder Anweisung, was bei Fehlern beim Batch-Abbruch nicht viel geholfen hat.

    TRY...CATCHführte jedoch einen neuen "Staat" ein. Wenn Sie das Konstrukt nicht verwenden TRY...CATCHund eine Transaktion aktiv ist und ein Fehler auftritt, können mehrere Pfade verwendet werden:

    • XACT_ABORT OFFund Fehler beim Abbrechen der Anweisung: Die Transaktion ist noch aktiv und die Verarbeitung wird mit der nächsten Anweisung fortgesetzt , sofern vorhanden.
    • XACT_ABORT OFFund die meisten Stapelabbruchfehler: Die Transaktion ist noch aktiv und die Verarbeitung wird mit dem nächsten Stapel fortgesetzt , sofern vorhanden.
    • XACT_ABORT OFFund bestimmte Stapelabbruchfehler: Die Transaktion wird zurückgesetzt und die Verarbeitung wird mit dem nächsten Stapel fortgesetzt , falls vorhanden.
    • XACT_ABORT ONund ein Fehler: Die Transaktion wird zurückgesetzt und die Verarbeitung wird mit dem nächsten Stapel fortgesetzt , falls vorhanden.


    Bei Verwendung von TRY...CATCHBatch-Abbruch-Fehlern wird der Batch jedoch nicht abgebrochen, sondern die Steuerung an den CATCHBlock übertragen. Wenn dies der Fall XACT_ABORTist OFF, wird die Transaktion die COMMITmeiste Zeit noch aktiv sein, und Sie müssen oder höchstwahrscheinlich ROLLBACK. Wenn jedoch bestimmte Batch-Abbruch-Fehler auftreten (z. B. mit OPENQUERY) oder wenn dies der Fall XACT_ABORTist ON, befindet sich die Transaktion in einem neuen Zustand, "unverbindlich". In diesem Status COMMITkönnen Sie keine DML-Operationen ausführen. Alles, was Sie tun können, ist ROLLBACKund SELECTAussagen. In diesem "nicht festlegbaren" Zustand wurde die Transaktion jedoch zurückgesetzt, nachdem ein Fehler aufgetreten ist, und die Ausstellung der Transaktion ROLLBACKist nur eine Formalität, die jedoch erledigt werden muss.

    Mit der Funktion XACT_STATE kann ermittelt werden, ob eine Transaktion aktiv, nicht festschreibbar oder nicht vorhanden ist. Es wird empfohlen (zumindest von einigen), diese Funktion im CATCHBlock zu überprüfen , um festzustellen, ob das Ergebnis -1(dh nicht festgeschrieben) ist, anstatt zu testen, ob @@TRANCOUNT > 0. Aber mit XACT_ABORT ON, das sollte der einzig mögliche Zustand sein, in dem man sein kann, also scheint es, als würde man auf @@TRANCOUNT > 0und XACT_STATE() <> 0gleichwertig prüfen . Auf der anderen Seite, wenn XACT_ABORTist OFFund es eine aktive Transaktion, dann ist es möglich , einen Zustand entweder zu haben 1oder -1in dem CATCHBlock, die die Ausgabe für die Möglichkeit erlaubt COMMITstatt ROLLBACK(obwohl ich nicht von einem Fall denken kann , wenn jemand würde wollenCOMMITwenn die Transaktion verbindlich ist). Weitere Informationen und Recherchen zur Verwendung XACT_STATE()innerhalb eines CATCHBlocks mit XACT_ABORT ONfinden Sie in meiner Antwort auf die folgende DBA.SE-Frage: In welchen Fällen kann eine Transaktion innerhalb des CATCH-Blocks festgeschrieben werden, wenn XACT_ABORT auf ON gesetzt ist? . Bitte beachten Sie, dass es einen kleinen Fehler gibt XACT_STATE(), der dazu führt, dass er 1in bestimmten Szenarien fälschlicherweise zurückgegeben wird : XACT_STATE () gibt 1 zurück, wenn es in SELECT mit einigen Systemvariablen, aber ohne FROM-Klausel verwendet wird


Anmerkungen zum Originalcode:

  • Sie können den der Transaktion zugewiesenen Namen entfernen, da dies für keine Transaktion hilfreich ist.
  • Sie brauchen das BEGINund ENDum jeden EXECAnruf nicht
Solomon Rutzky
quelle
2
Es ist eine wirklich gute, gute Antwort.
McNets
1
Wow, das ist eine umfassende Antwort! Vielen Dank! Übrigens, die folgende Seite behandelt die Fehler, die Sie anspielen und die nicht von Try ... Catch? (Unter der Überschrift "Von einem TRY… CATCH-Konstrukt nicht
betroffene
1
@jrdevdba Danke :-). Und herzlich willkommen. In Bezug auf die Fehler, die nicht gefangen wurden, habe ich diese beiden so ziemlich gemeint: Compile errors, such as syntax errors, that prevent a batch from runningund Errors that occur during statement-level recompilation, such as object name resolution errors that occur after compilation because of deferred name resolution.. Aber sie kommen nicht sehr oft vor, und wenn Sie eine solche Situation finden, beheben Sie sie entweder (wenn es sich um einen Fehler im Code handelt) oder platzieren Sie sie in einem Unterprozess ( EXECoder sp_executesql), damit TRY...CATCHsie abgefangen werden können.
Solomon Rutzky
2

Ja, wenn aufgrund eines Fehler-Rollback-Codes in der catch-Anweisung Ihrer gespeicherten Hauptprozedur ein Rollback aller Operationen ausgeführt wird, die von einer direkten Anweisung oder von einer Ihrer darin verschachtelten gespeicherten Prozeduren ausgeführt werden.

Auch wenn Sie in Ihren verschachtelten gespeicherten Prozeduren keine explizite Transaktion angewendet haben, verwendet diese gespeicherte Prozedur dennoch eine implizite Transaktion und schreibt sie nach Abschluss fest, ABER entweder Sie haben eine explizite oder eine implizite Transaktion in verschachtelten gespeicherten gespeicherten Prozeduren festgeschrieben Rollback aller Aktionen dieser verschachtelten gespeicherten Prozeduren, wenn die gespeicherte Hauptprozedur fehlschlägt und die Transaktion rückgängig gemacht wird.

Jedes Mal, wenn die Transaktion festgeschrieben oder zurückgesetzt wird, basierend auf der Aktion, die am Ende der äußersten Transaktion ausgeführt wurde. Wenn die äußere Transaktion festgeschrieben ist, werden auch die inneren geschachtelten Transaktionen festgeschrieben. Wenn die äußere Transaktion zurückgesetzt wird, werden auch alle inneren Transaktionen zurückgesetzt, unabhängig davon, ob die inneren Transaktionen einzeln festgeschrieben wurden oder nicht.

Als Referenz http://technet.microsoft.com/en-us/library/ms189336(v=sql.105).aspx

aasim.abdullah
quelle