Транзакция в хранимой процедуре

12

Мне нужно выполнить ОБНОВЛЕНИЕ и ВСТАВКУ в одной транзакции. Этот код прекрасно работает сам по себе, но я хотел бы иметь возможность легко вызывать его и передавать необходимые параметры. Когда я пытаюсь вложить эту транзакцию в хранимую процедуру, я сталкиваюсь с множеством синтаксических ошибок.

Как я могу инкапсулировать следующий код, чтобы его можно было легко вызвать?

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
Вероятно, это поможет, если вы добавите в свой вопрос подробности о том, что именно являются «ошибками» (воспользуйтесь ссылкой « Изменить» под вашим вопросом). Также, пожалуйста, возьмите тур . Спасибо!
Макс Вернон

Ответы:

15

Вам нужно обернуть этот код в CREATE PROCEDURE ...синтаксис и удалить GOоператоры после BEGIN TRANSACTIONи до 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

Также обратите внимание, что я добавил TRY...CATCHблок операторов, чтобы разрешить выполнение ROLLBACK TRANSACTIONоператора в случае возникновения ошибки. Возможно, вам нужна лучшая обработка ошибок, но без знания ваших требований это в лучшем случае сложно.

Некоторое хорошее чтение:

  1. Всегда указывайте схему

  2. Хранимая процедура Best Practices

  3. Вредных привычек, которых следует избегать

Макс Вернон
источник
1
Вы все еще хотите сохранить сохраненную транзакцию. Если вы поместите транзакцию в SP, а SP будет помещен в другую транзакцию, произойдет сбой. sqlstudies.com/2014/01/06/…
Кеннет Фишер
спасибо, что указал мне на это. Я знаю, что в SQL Server нет вложенных транзакций, однако я не знал о SAVE TRANSпоследствиях команды.
Макс Вернон
8

Если вы хотите правильно обрабатывать вложенные хранимые процедуры, которые могут обрабатывать транзакции (независимо от того, были ли они запущены из T-SQL или из кода приложения), вы должны следовать шаблону, который я описал в следующем ответе:

Должны ли мы обрабатывать транзакции в коде C #, а также в хранимых процедурах

Вы заметите два отличия от того, что вы пытаетесь здесь:

  1. Использование RAISERRORвнутри CATCHблока. Это приводит к возникновению ошибки до уровня вызова (будь то на уровне БД или на уровне приложения), поэтому можно принять решение относительно того, что произошла ошибка.

  2. Нет SAVE TRANSACTION. Я никогда не нашел случая использовать это. Я знаю, что некоторые люди предпочитают это, но во всем, что я когда-либо делал в любом месте, где я работал, понятие ошибки, возникающей на любом из вложенных уровней, подразумевало, что любая работа, которая уже была сделана, была недействительной. При использовании SAVE TRANSACTIONвы возвращаетесь в состояние только перед вызовом этой хранимой процедуры, в результате чего существующий процесс остается действительным.

    Если вы хотите получить более подробную информацию SAVE TRANSACTION, ознакомьтесь с информацией в этом ответе:

    Как выполнить откат при запуске 3 хранимых процедур из одной хранимой процедуры

    Другая проблема, с SAVE TRANSACTIONкоторой он сталкивается - это нюанс его поведения, как отмечено на странице MSDN для SAVE TRANSACTION (выделение добавлено):

    В транзакции допускаются повторяющиеся имена точек сохранения, но оператор ROLLBACK TRANSACTION, который задает имя точки сохранения, будет выполнять откат транзакции только до самой последней операции SAVE TRANSACTION с использованием этого имени.

    Это означает, что вы должны быть очень осторожны, чтобы дать каждой точке сохранения в каждой хранимой процедуре имя, уникальное для всех точек сохранения во всех хранимых процедурах. Следующие примеры иллюстрируют эту точку зрения.

    Этот первый пример показывает, что происходит при повторном использовании имени точки сохранения; откатывается только точка сохранения самого низкого уровня.

    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

    Этот второй пример показывает, что происходит, когда вы используете уникальные имена точек сохранения; Точка сохранения нужного уровня откатывается.

    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>
Соломон Руцкий
источник
Вот почему я использую @@ NESTLEVEL для изготовления моего имени точки сохранения SAVE TRANSACTION.
Винсент Ванкалберг