Transazione in una stored procedure


12

Devo eseguire un AGGIORNAMENTO e un INSERT in una singola transazione. Quel codice funziona bene da solo, ma mi piacerebbe essere in grado di chiamarlo facilmente e passare i parametri richiesti. Quando provo a nidificare questa transazione in una procedura memorizzata, mi imbatto in molti errori di sintassi.

Come posso incapsulare il seguente codice in modo che possa essere facilmente chiamato?

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

1
Sarebbe probabilmente utile se aggiungessi dettagli alla tua domanda su cosa siano esattamente gli "errori" (usa il link di modifica sotto la tua domanda). Inoltre, ti preghiamo di fare il tour . Grazie!
Max Vernon,

Risposte:


15

Ti piace dover racchiudere quel codice nella CREATE PROCEDURE ...sintassi e rimuovere le GOistruzioni dopo BEGIN TRANSACTIONe prima 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

Inoltre, ho aggiunto un TRY...CATCHblocco di istruzioni per consentire l'esecuzione di una ROLLBACK TRANSACTIONdichiarazione nel caso si verifichi un errore. Probabilmente hai bisogno di una gestione degli errori migliore di quella, ma senza la conoscenza delle tue esigenze, è difficile nella migliore delle ipotesi.

Qualche buona lettura:

  1. Specifica sempre lo schema

  2. Best practice per stored procedure

  3. Cattive abitudini da evitare


1
Vuoi ancora avere una transazione salvata. Se si inserisce una transazione in un SP e il SP viene spostato in un'altra transazione, le cose non andranno a buon fine. sqlstudies.com/2014/01/06/…
Kenneth Fisher

grazie per avermi indicato. So che non ci sono transazioni nidificate in SQL Server, tuttavia non ero a conoscenza delle SAVE TRANSimplicazioni del comando.
Max Vernon,

8

Se si desidera gestire correttamente le stored procedure nidificate in grado di gestire le transazioni (sia avviate da T-SQL o dal codice dell'app), è necessario seguire il modello che ho descritto nella seguente risposta:

Ci viene richiesto di gestire la transazione nel codice C # e nella procedura memorizzata

Noterai due differenze lì rispetto a ciò che stai tentando qui:

  1. L'uso RAISERRORall'interno del CATCHblocco. Questo elimina l'errore fino al livello chiamante (sia nel DB che nel livello app), quindi è possibile prendere una decisione in merito al fatto che si sia verificato un errore.

  2. No SAVE TRANSACTION. Non ho mai trovato un caso per l'utilizzo di questo. So che alcune persone lo preferiscono, ma in tutto ciò che ho mai fatto in qualsiasi posto in cui ho lavorato, l'idea di un errore che si verifica all'interno di uno dei livelli nidificati implicava che qualunque lavoro fosse già stato fatto non era valido. Usando SAVE TRANSACTIONsi ritorna allo stato appena prima che venga chiamata questa procedura memorizzata, lasciando il processo esistente come altrimenti valido.

    Se desideri maggiori dettagli su SAVE TRANSACTION, dai un'occhiata alle informazioni in questa risposta:

    Come eseguire il rollback quando vengono avviate 3 stored procedure da una stored procedure

    Un altro problema con SAVE TRANSACTIONè una sfumatura del suo comportamento, come notato nella pagina MSDN per SALVA TRANSAZIONE (enfasi aggiunta):

    Nomi punto di salvataggio duplicati sono consentiti in una transazione, ma un TRANSACTION ROLLBACK che specifica il nome punto di salvataggio rotoleranno solo la parte posteriore transazione al più recente OPERAZIONE SALVA con quel nome.

    Significato, è necessario prestare molta attenzione a assegnare a ciascun punto di salvataggio in ciascuna procedura memorizzata un nome univoco in tutti i punti di salvataggio in tutte le procedure memorizzate. I seguenti esempi illustrano questo punto.

    Questo primo esempio mostra cosa succede quando riutilizzi il nome del punto di salvataggio; viene eseguito il rollback solo del punto di salvataggio di livello più basso.

    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
    

    Questo secondo esempio mostra cosa succede quando si usano nomi univoci di punti di salvataggio; il punto di salvataggio del livello desiderato viene ripristinato.

    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>
    

Ecco perché uso @@ NESTLEVEL per fabbricare il mio nome di salvataggio SAVE TRANSACTION.
Vincent Vancalbergh,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.