Transaktion in einer gespeicherten Prozedur

12

Ich muss ein UPDATE und ein INSERT in einer einzigen Transaktion ausführen. Dieser Code funktioniert von sich aus einwandfrei, aber ich möchte ihn einfach aufrufen und die erforderlichen Parameter übergeben können. Wenn ich versuche, diese Transaktion in einer gespeicherten Prozedur zu verschachteln, treten viele Syntaxfehler auf.

Wie kann ich den folgenden Code kapseln, damit er einfach aufgerufen werden kann?

BEGIN TRANSACTION AssignUserToTicket
GO

DECLARE @updateAuthor varchar(100)
DECLARE @assignedUser varchar(100)
DECLARE @ticketID bigint

SET @updateAuthor = 'user1'
SET @assignedUser = 'user2'
SET @ticketID = 123456

    UPDATE tblTicket SET ticketAssignedUserSamAccountName = @assignedUser WHERE (ticketID = @ticketID);
    INSERT INTO [dbo].[tblTicketUpdate]
           ([ticketID]
           ,[updateDetail]
           ,[updateDateTime]
           ,[userSamAccountName]
           ,[activity])
     VALUES
           (@ticketID,
           'Assigned ticket to ' + @assignedUser,
           GetDate(),
           @updateAuthor,
           'Assign');
GO
COMMIT TRANSACTION AssignUserToTicket
Charlie K.
quelle
1
Es wäre wahrscheinlich hilfreich, wenn Sie Ihrer Frage Details zu den "Fehlern" hinzufügen würden (verwenden Sie den Bearbeitungslink unter Ihrer Frage). Bitte nehmen Sie auch an der Tour teil . Vielen Dank!
Max Vernon

Antworten:

15

Sie möchten diesen Code in CREATE PROCEDURE ...Syntax einschließen und die GOAnweisungen nach BEGIN TRANSACTIONund vor entfernen COMMIT TRANSACTION.

GO
CREATE PROCEDURE dbo.AssignUserToTicket
(
     @updateAuthor varchar(100)
    , @assignedUser varchar(100)
    , @ticketID bigint
)
AS
BEGIN
    BEGIN TRANSACTION;
    SAVE TRANSACTION MySavePoint;
    SET @updateAuthor = 'user1';
    SET @assignedUser = 'user2';
    SET @ticketID = 123456;

    BEGIN TRY
        UPDATE dbo.tblTicket 
        SET ticketAssignedUserSamAccountName = @assignedUser 
        WHERE (ticketID = @ticketID);

        INSERT INTO [dbo].[tblTicketUpdate]
            (
            [ticketID]
            ,[updateDetail]
            ,[updateDateTime]
            ,[userSamAccountName]
            ,[activity]
            )
        VALUES (
            @ticketID
            , 'Assigned ticket to ' + @assignedUser
            , GetDate()
            , @updateAuthor
            , 'Assign'
            );
        COMMIT TRANSACTION 
    END TRY
    BEGIN CATCH
        IF @@TRANCOUNT > 0
        BEGIN
            ROLLBACK TRANSACTION MySavePoint; -- rollback to MySavePoint
        END
    END CATCH
END;
GO

Beachten Sie auch, dass ich einen TRY...CATCHAnweisungsblock hinzugefügt habe , um das Ausführen einer ROLLBACK TRANSACTIONAnweisung für den Fall eines Fehlers zu ermöglichen . Sie benötigen wahrscheinlich eine bessere Fehlerbehandlung, aber ohne Kenntnis Ihrer Anforderungen ist dies bestenfalls schwierig.

Einige gute Lektüre:

  1. Geben Sie immer das Schema an

  2. Best Practices für gespeicherte Verfahren

  3. Schlechte Gewohnheiten zu vermeiden

Max Vernon
quelle
1
Sie möchten weiterhin eine gespeicherte Transaktion haben. Wenn Sie eine Transaktion in einen SP einfügen und der SP in eine andere Transaktion eingeschlossen wird, schlägt dies fehl. sqlstudies.com/2014/01/06/…
Kenneth Fisher
Danke, dass du mich darauf hingewiesen hast. Ich weiß, dass es in SQL Server keine verschachtelten Transaktionen gibt, war mir jedoch der SAVE TRANSAuswirkungen des Befehls nicht bewusst .
Max Vernon
8

Wenn Sie verschachtelte gespeicherte Prozeduren, die Transaktionen verarbeiten können (unabhängig davon, ob sie mit T-SQL oder App-Code gestartet wurden), ordnungsgemäß verarbeiten möchten, sollten Sie der Vorlage folgen, die ich in der folgenden Antwort beschrieben habe:

Müssen wir Transaktionen sowohl im C # -Code als auch in gespeicherten Prozeduren abwickeln?

Sie werden dort zwei Unterschiede zu dem bemerken, was Sie hier versuchen:

  1. Die Verwendung RAISERRORinnerhalb des CATCHBlocks. Dadurch wird der Fehler auf die aufrufende Ebene (ob in der DB- oder App-Ebene) übertragen, sodass eine Entscheidung darüber getroffen werden kann, ob ein Fehler aufgetreten ist.

  2. Nein SAVE TRANSACTION. Ich habe nie einen Grund dafür gefunden. Ich weiß, dass einige Leute es vorziehen, aber bei allem, was ich jemals an einem Ort getan habe, an dem ich gearbeitet habe, implizierte die Vorstellung eines Fehlers, der in einer der verschachtelten Ebenen auftrat, dass die bereits geleistete Arbeit ungültig war. Durch die Verwendung SAVE TRANSACTIONkehren Sie erst in den Status zurück, bevor diese gespeicherte Prozedur aufgerufen wurde, und belassen den vorhandenen Prozess als anderweitig gültig.

    Wenn Sie weitere Informationen zu wünschen SAVE TRANSACTION, lesen Sie bitte die Informationen in dieser Antwort:

    Zurücksetzen, wenn 3 gespeicherte Prozeduren von einer gespeicherten Prozedur gestartet werden

    Ein weiteres Problem SAVE TRANSACTIONist eine Nuance des Verhaltens, wie auf der MSDN-Seite für SAVE TRANSACTION (Hervorhebung hinzugefügt) angegeben:

    Doppelte Sicherungspunktnamen sind in einer Transaktion zulässig, aber eine ROLLBACK TRANSACTION-Anweisung, die den Sicherungspunktnamen angibt, setzt die Transaktion nur unter Verwendung dieses Namens auf die letzte SAVE TRANSACTION zurück.

    Das heißt, Sie müssen sehr vorsichtig sein, um jedem Speicherpunkt in jeder gespeicherten Prozedur einen Namen zu geben, der für alle Speicherpunkte in allen gespeicherten Prozeduren eindeutig ist. Die folgenden Beispiele veranschaulichen diesen Punkt.

    Dieses erste Beispiel zeigt, was passiert, wenn Sie den Namen des Speicherpunkts wiederverwenden. Nur der Speicherpunkt der niedrigsten Ebene wird zurückgesetzt.

    IF (OBJECT_ID(N'tempdb..#SaveTranTestA') IS NOT NULL)
    BEGIN
        DROP TABLE #SaveTranTestA;
    END;
    CREATE TABLE #SaveTranTestA (SomeVal INT NOT NULL);
    
    BEGIN TRAN; -- start level 1
    SAVE TRANSACTION MySavePoint;
    
    SELECT @@TRANCOUNT AS [TranCount]; -- 1
    
    INSERT INTO #SaveTranTestA (SomeVal) VALUES (100);
    
    BEGIN TRAN; -- start level 2
    SAVE TRANSACTION MySavePoint;
    
    SELECT @@TRANCOUNT AS [TranCount]; -- 2
    
    INSERT INTO #SaveTranTestA (SomeVal) VALUES (200);
    
    COMMIT; -- exit level 2
    
    SELECT @@TRANCOUNT AS [TranCount]; -- 1
    SELECT * FROM #SaveTranTestA;
    -- 100
    -- 200
    
    ROLLBACK TRANSACTION MySavePoint; -- error occurred; undo actions up to this point
    
    SELECT @@TRANCOUNT AS [TranCount]; -- 1
    SELECT * FROM #SaveTranTestA;
    -- 100
    
    COMMIT; -- exit level 1
    
    SELECT @@TRANCOUNT AS [TranCount]; -- 0
    SELECT * FROM #SaveTranTestA;
    -- 100

    Dieses zweite Beispiel zeigt, was passiert, wenn Sie eindeutige Speicherpunktnamen verwenden. Der Speicherpunkt des gewünschten Levels wird zurückgesetzt.

    IF (OBJECT_ID(N'tempdb..#SaveTranTestB') IS NOT NULL)
    BEGIN
        DROP TABLE #SaveTranTestB;
    END;
    CREATE TABLE #SaveTranTestB (SomeVal INT NOT NULL);
    
    BEGIN TRAN; -- start level 1
    SAVE TRANSACTION MySavePointUno;
    
    SELECT @@TRANCOUNT AS [TranCount]; -- 1
    
    INSERT INTO #SaveTranTestB (SomeVal) VALUES (100);
    
    BEGIN TRAN; -- start level 2
    SAVE TRANSACTION MySavePointDos;
    
    SELECT @@TRANCOUNT AS [TranCount]; -- 2
    
    INSERT INTO #SaveTranTestB (SomeVal) VALUES (200);
    
    COMMIT; -- exit level 2
    
    SELECT @@TRANCOUNT AS [TranCount]; -- 1
    SELECT * FROM #SaveTranTestB;
    -- 100
    -- 200
    
    ROLLBACK TRANSACTION MySavePointUno; --error occurred; undo actions up to this point
    
    SELECT @@TRANCOUNT AS [TranCount]; -- 1
    SELECT * FROM #SaveTranTestB;
    -- <no rows>
    
    COMMIT; -- exit level 1
    
    SELECT @@TRANCOUNT AS [TranCount]; -- 0
    SELECT * FROM #SaveTranTestB;
    -- <no rows>
Solomon Rutzky
quelle
Aus diesem Grund verwende ich @@ NESTLEVEL, um meinen SAVE TRANSACTION-Sicherungspunktnamen zu erstellen.
Vincent Vancalbergh