ストアドプロシージャでのトランザクション


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

回答:


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#コードとストアドプロシージャの両方で処理する必要がありますか

ここで試みているものとは2つの違いがあることに気づくでしょう。

  1. ブロックRAISERROR内での使用CATCH。これにより、エラーが呼び出しレベルまで(DB層またはアプリ層のいずれかで)バブリングされるため、エラーが発生したという事実に関して決定を下すことができます。

  2. いいえSAVE TRANSACTION。私はこれを使用するためのケースを見つけたことがありません。私は一部の人々がそれを好むことを知っていますが、私が働いたことのある場所でこれまでに行ったことすべてにおいて、ネストされたレベルのいずれかでエラーが発生するという概念は、すでに行われた作業が無効であることを意味しました。を使用SAVE TRANSACTIONすると、このストアドプロシージャが呼び出される直前の状態に戻るだけで、既存のプロセスは他の方法で有効なままになります。

    の詳細が必要な場合はSAVE TRANSACTION、この回答の情報をご覧ください。

    1つのストアドプロシージャから3つのストアドプロシージャが開始されたときにロールバックする方法

    もう1つの問題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

    この2番目の例は、一意のセーブポイント名を使用するとどうなるかを示しています。目的のレベルのセーブポイントがロールバックされます。

    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
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.