web-dev-qa-db-ja.com

トランザクションの保存とトランザクションの開始(SQL Server)トランザクションを適切にネストする方法

特定の状況下で、実行したすべてを元に戻してエラーコードを呼び出し元に返すか、受け入れ/コミットして成功を呼び出し元に返すことができるように、セーブポイントを設定する必要があるストアドプロシージャがあります。しかし、発信者がすでにトランザクションを開始しているかどうかに関係なく、機能する必要があります。ドキュメントは、このテーマに関して非常に混乱しています。これがうまくいくと思うことですが、すべての影響については確信がありません。

問題は、このStored Procedure (SP)が他の人から呼び出されていることです。そのため、ユーザーがトランザクションを開始したかどうかはわかりません...ユーザーにSPを使用するためにトランザクションを開始するように要求した場合でも、Save Pointsの適切な使用について質問があります...

My SPは、トランザクションが進行中であるかどうかをテストし、進行中でない場合は、BEGIN TRANSACTIONで開始します。トランザクションがすでに進行中の場合は、代わりにSAVE TRANSACTION MySavePointNameで保存ポイントを作成し、実際、これは私がしたことです。

次に、変更をロールバックする必要がある場合、以前にBEGIN TRANSACTIONを実行した場合は、ROLLBACK TRANSACTIONを実行します。セーブポイントを実行した場合は、ROLLBACK TRANSACTION MySavePointNameします。このシナリオはうまく機能しているようです。

ここで少し混乱します。これまでに行った作業を維持したい場合は、トランザクションを開始した場合はCOMMIT TRANSACTIONを実行します。しかし、セーブポイントを作成した場合はどうなりますか? COMMIT TRANSACTION MySavePointNameを試しましたが、呼び出し元がトランザクションをコミットしようとしてエラーが発生します。

COMMIT TRANSACTION要求には、対応するBEGINTRANSACTIONがありません。

だから私は疑問に思っています-セーブポイントはロールバックできます(それは機能します:ROLLBACK TRANSACTION MySavePointNameは呼び出し元のトランザクションをロールバックしません)。しかし、おそらくそれを「コミット」する必要はないのでしょうか。ロールバックする必要がある場合に備えて、そのまま残りますが、元のトランザクションがコミット(またはロールバック)されると消えますか?

トランザクションを「ネスト」する「より良い」方法がある場合は、同様にいくつかの光を当ててください。 BEGIN TRANSACTIONでネストする方法がわかりませんが、内部トランザクションをロールバックまたはコミットするだけです。 ROLLBACKは常に最上位のトランザクションにロールバックするようですが、COMMITは単に@@trancountをデクリメントします。

15
Brian B

私はこれをすべて理解したと信じているので、私自身の質問に答えます...

詳細が必要な場合は、調査結果をブログに投稿しました http://geekswithblogs.net/bbiales/archive/2012/03/15/how-to-nest-transactions-nicely---quotbegin-transactionquo​​t -vs-quotsave.aspx

したがって、私のSPは次のようなもので始まり、新しいトランザクションがない場合は開始しますが、すでに進行中の場合はセーブポイントを使用します。

DECLARE @startingTranCount int
SET @startingTranCount = @@TRANCOUNT

IF @startingTranCount > 0
    SAVE TRANSACTION mySavePointName
ELSE
    BEGIN TRANSACTION
-- …

次に、変更をコミットする準備ができたら、トランザクションを自分で開始した場合にのみコミットする必要があります。

IF @startingTranCount = 0
    COMMIT TRANSACTION

そして最後に、これまでの変更だけをロールバックするには:

-- Roll back changes...
IF @startingTranCount > 0
    ROLLBACK TRANSACTION MySavePointName
ELSE
    ROLLBACK TRANSACTION
22
Brian B

拡張 ブライアンBの答え

これにより、保存ポイント名が一意になり、SQL Server2012の新しいTRY/CATCH/THROW機能が使用されます。

DECLARE @mark CHAR(32) = replace(newid(), '-', '');
DECLARE @trans INT = @@TRANCOUNT;

IF @trans = 0
    BEGIN TRANSACTION @mark;
ELSE
    SAVE TRANSACTION @mark;

BEGIN TRY
    -- do work here

    IF @trans = 0
        COMMIT TRANSACTION @mark;
END TRY
BEGIN CATCH
    IF xact_state() = 1 OR (@trans = 0 AND xact_state() <> 0) ROLLBACK TRANSACTION @mark;
    THROW;
END CATCH
12
Chris Chilvers

このタイプのトランザクションマネージャーをストアドプロシージャで使用しました:

    CREATE PROCEDURE Ardi_Sample_Test  
        @InputCandidateID INT  
    AS  
        DECLARE @TranCounter INT;  
        SET @TranCounter = @@TRANCOUNT;  
        IF @TranCounter > 0  
            SAVE TRANSACTION ProcedureSave;  
        ELSE  
            BEGIN TRANSACTION;  
        BEGIN TRY  

            /*
            <Your Code>
            */

            IF @TranCounter = 0  
                COMMIT TRANSACTION;  
        END TRY  
        BEGIN CATCH  
            IF @TranCounter = 0  
                ROLLBACK TRANSACTION;  
            ELSE  
                IF XACT_STATE() <> -1  
                    ROLLBACK TRANSACTION ProcedureSave;  

            DECLARE @ErrorMessage NVARCHAR(4000);  
            DECLARE @ErrorSeverity INT;  
            DECLARE @ErrorState INT;  
            SELECT @ErrorMessage = ERROR_MESSAGE();  
            SELECT @ErrorSeverity = ERROR_SEVERITY();  
            SELECT @ErrorState = ERROR_STATE();  

            RAISERROR (@ErrorMessage, @ErrorSeverity, @ErrorState);  
        END CATCH  
    GO  
3