Frage

Ich habe eine gespeicherte Prozedur, die einen Speicherpunkt festlegen muss, damit sie unter bestimmten Umständen alles, was sie getan hat, rückgängig machen und einen Fehlercode an den Aufrufer zurückgeben oder ihn akzeptieren/festschreiben und dem Aufrufer Erfolg zurückgeben kann.Aber ich brauche es, um zu funktionieren, unabhängig davon, ob der Anrufer bereits eine Transaktion gestartet hat oder nicht.Das Dokument ist zu diesem Thema äußerst verwirrend.Hier ist, was meiner Meinung nach funktionieren wird, aber ich bin mir nicht sicher über alle Konsequenzen.

Die Sache ist - das Stored Procedure (SP) wird von anderen aufgerufen.Ich weiß also nicht, ob sie eine Transaktion gestartet haben oder nicht ...Selbst wenn ich verlange, dass Benutzer eine Transaktion starten, um meinen SP zu nutzen, habe ich immer noch Fragen zur ordnungsgemäßen Nutzung von Save Points ...

Mein SP testet, ob eine Transaktion läuft, und wenn nicht, startet er eine BEGIN TRANSACTION.Wenn eine Transaktion bereits ausgeführt wird, wird stattdessen ein Speicherpunkt mit erstellt SAVE TRANSACTION MySavePointName, und sparen Sie sich die Tatsache, dass ich das getan habe.

Wenn ich dann meine Änderungen rückgängig machen muss, wenn ich a BEGIN TRANSACTION früher, dann werde ich es tun ROLLBACK TRANSACTION.Wenn ich den Speicherpunkt geschafft habe, dann werde ich es tun ROLLBACK TRANSACTION MySavePointName.Dieses Szenario scheint großartig zu funktionieren.

Hier bin ich etwas verwirrt: Wenn ich meine geleistete Arbeit behalten möchte, führe ich eine Transaktion aus, wenn ich sie gestartet habe COMMIT TRANSACTION.Aber wenn ich einen Speicherpunkt erstellt habe?Ich habe es versucht COMMIT TRANSACTION MySavePointName, aber dann versucht der Aufrufer, seine Transaktion festzuschreiben, und erhält eine Fehlermeldung:

Die COMMIT TRANSACTION-Anfrage hat keine entsprechende BEGIN TRANSACTION.

Ich frage mich also: Ein Speicherpunkt kann zurückgesetzt werden (das funktioniert: ROLLBACK TRANSACTION MySavePointName wird die Transaktion des Anrufers NICHT rückgängig machen).Aber vielleicht muss man es nie „begehen“?Es bleibt einfach dort, für den Fall, dass Sie darauf zurücksetzen müssen, verschwindet aber, sobald die ursprüngliche Transaktion festgeschrieben (oder zurückgesetzt) ​​wurde?

Wenn es eine „bessere“ Möglichkeit gibt, eine Transaktion zu „verschachteln“, bringen Sie bitte ebenfalls etwas Licht ins Dunkel.Ich habe nicht herausgefunden, wie ich damit nisten soll BEGIN TRANSACTION aber nur Rollback oder Commit meiner internen Transaktion.Scheint ROLLBACK wird immer zur obersten Transaktion zurückgesetzt, while COMMIT einfach dekrementiert @@trancount.

War es hilfreich?

Lösung

Ich glaube, ich habe das jetzt alles herausgefunden, deshalb werde ich meine Frage selbst beantworten ...

Ich habe meine Ergebnisse sogar gebloggt, falls Sie weitere Einzelheiten wünschen unter http://geekswithblogs.net/bbiales/archive/2012/03/15/how-to-nest-transactions-nicely---quotbegin-transactionquot-vs-quotsave.aspx

Mein SP beginnt also mit etwas wie diesem, um eine neue Transaktion zu starten, wenn keine vorhanden ist, aber einen Speicherpunkt zu verwenden, wenn bereits eine im Gange ist:

DECLARE @startingTranCount int
SET @startingTranCount = @@TRANCOUNT

IF @startingTranCount > 0
    SAVE TRANSACTION mySavePointName
ELSE
    BEGIN TRANSACTION
-- …

Wenn Sie dann bereit sind, die Änderungen zu übernehmen, müssen Sie diese nur dann übernehmen, wenn wir die Transaktion selbst gestartet haben:

IF @startingTranCount = 0
    COMMIT TRANSACTION

Und schließlich, um nur Ihre bisherigen Änderungen rückgängig zu machen:

-- Roll back changes...
IF @startingTranCount > 0
    ROLLBACK TRANSACTION MySavePointName
ELSE
    ROLLBACK TRANSACTION

Andere Tipps

Erweitern Brian Bs Antwort.

Dadurch wird sichergestellt, dass der Speicherpunktname eindeutig ist und die neuen TRY/CATCH/THROW-Funktionen von SQL Server 2012 verwendet werden.

DECLARE @mark CHAR(32) = replace(newid(), '-', '');
DECLARE @trans INT = @@TRANCOUNT;

IF @trans = 0
    BEGIN TRANSACTION @mark;
ELSE
    SAVE TRANSACTION @mark;

BEGIN TRY
    -- do work here

    IF @trans = 0
        COMMIT TRANSACTION @mark;
END TRY
BEGIN CATCH
    IF xact_state() = 1 OR (@trans = 0 AND xact_state() <> 0) ROLLBACK TRANSACTION @mark;
    THROW;
END CATCH

Ich habe diese Art von Transaktionsmanager in meinen gespeicherten Prozeduren verwendet:

    CREATE PROCEDURE Ardi_Sample_Test  
        @InputCandidateID INT  
    AS  
        DECLARE @TranCounter INT;  
        SET @TranCounter = @@TRANCOUNT;  
        IF @TranCounter > 0  
            SAVE TRANSACTION ProcedureSave;  
        ELSE  
            BEGIN TRANSACTION;  
        BEGIN TRY  

            /*
            <Your Code>
            */

            IF @TranCounter = 0  
                COMMIT TRANSACTION;  
        END TRY  
        BEGIN CATCH  
            IF @TranCounter = 0  
                ROLLBACK TRANSACTION;  
            ELSE  
                IF XACT_STATE() <> -1  
                    ROLLBACK TRANSACTION ProcedureSave;  

            DECLARE @ErrorMessage NVARCHAR(4000);  
            DECLARE @ErrorSeverity INT;  
            DECLARE @ErrorState INT;  
            SELECT @ErrorMessage = ERROR_MESSAGE();  
            SELECT @ErrorSeverity = ERROR_SEVERITY();  
            SELECT @ErrorState = ERROR_STATE();  

            RAISERROR (@ErrorMessage, @ErrorSeverity, @ErrorState);  
        END CATCH  
    GO  
Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top