存储过程中的事务

Cha*_*e K 13 sql-server stored-procedures t-sql transaction sql-server-2012

我需要在单个事务中执行 UPDATE 和 INSERT。该代码本身运行良好,但我希望能够轻松调用它并传入所需的参数。当我尝试将这个事务嵌套在一个存储过程中时,我遇到了很多语法错误。

如何封装以下代码以便轻松调用?

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
Run Code Online (Sandbox Code Playgroud)

Han*_*non 21

您需要用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
Run Code Online (Sandbox Code Playgroud)

另请注意,我添加了一个TRY...CATCH语句块以允许ROLLBACK TRANSACTION在发生某些错误时执行语句。您可能需要比这更好的错误处理,但如果不了解您的要求,那充其量是困难的。

一些不错的阅读:

  1. 始终指定架构

  2. 存储过程最佳实践

  3. 应避免的坏习惯

  • 您仍然希望保存交易。如果你把一个事务放在一个 SP 中,而这个 SP 被另一个事务包裹起来,那么东西就会失败。http://sqlstudies.com/2014/01/06/transactions-rolling-back-a-transaction-inside-a-stored-procedure/ (2认同)

Sol*_*zky 9

如果您想正确处理可以处理事务的嵌套存储过程(无论是从 T-SQL 还是应用程序代码开始),那么您应该遵循我在以下答案中描述的模板:

我们是否需要在 C# 代码和存储过程中处理事务

您会注意到与您在此处尝试的内容有两个不同之处:

  1. RAISERRORCATCH块内使用。这会将错误冒泡到调用级别(无论是在 DB 还是应用程序层),因此可以就发生错误的事实做出决定。

  2. 没有SAVE TRANSACTION。我从来没有找到使用它的案例。我知道有些人更喜欢它,但是在我在我工作过的任何地方做过的所有事情中,任何嵌套级别中发生错误的概念意味着已经完成的任何工作都是无效的。通过使用,SAVE TRANSACTION您只会恢复到调用此存储过程之前的状态,而使现有过程保持有效。

    如果您想了解有关 的更多详细信息SAVE TRANSACTION,请查看此答案中的信息:

    从一个存储过程启动3个存储过程时如何回滚

    另一个问题SAVE TRANSACTION是其行为的细微差别,如SAVE TRANSACTION的 MSDN 页面中所述(强调已添加):

    事务中允许使用重复的保存点名称,但指定保存点名称的 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
    
    Run Code Online (Sandbox Code Playgroud)

    第二个例子展示了当你使用唯一的保存点名称时会发生什么;所需级别的保存点被回滚。

    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>
    
    Run Code Online (Sandbox Code Playgroud)