存储过程中的事务


12

我需要在单个事务中执行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

1
如果您在问题中添加有关“错误”的确切含义的详细信息,可能会有所帮助(使用问题下方的编辑链接)。另外,请参加游览。谢谢!
Max Vernon

Answers:


15

您需要将代码包装成CREATE PROCEDURE ...语法,然后删除之前和之后的GO语句。BEGIN TRANSACTIONCOMMIT 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. 存储过程最佳实践

  3. 避免不良习惯


1
您仍然希望保存交易。如果将事务放入SP中,并且SP被另一个事务包裹,则东西将失败。 sqlstudies.com/2014/01/06/…–
肯尼思·费舍尔

谢谢你指出我的意思。我知道SQL Server中没有嵌套事务,但是我不知道该SAVE TRANS命令的含义。
Max Vernon

8

如果要正确处理可以处理事务的嵌套存储过程(无论是从T-SQL还是从应用程序代码启动),则应遵循以下答案中描述的模板:

我们是否需要使用C#代码以及存储过程来处理事务

您会发现与此处尝试的内容有两个区别:

  1. RAISERRORCATCH块内使用。这会使错误上升到调用级别(无论是在数据库层还是在应用程序层),因此可以针对发生错误的事实做出决定。

  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保存点名称的原因。
Vincent Vancalbergh
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.