特定の状況下で、実行したすべてを元に戻してエラーコードを呼び出し元に返すか、受け入れ/コミットして成功を呼び出し元に返すことができるように、セーブポイントを設定する必要があるストアドプロシージャがあります。しかし、発信者がすでにトランザクションを開始しているかどうかに関係なく、機能する必要があります。ドキュメントは、このテーマに関して非常に混乱しています。これがうまくいくと思うことですが、すべての影響については確信がありません。
問題は、この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
をデクリメントします。
私はこれをすべて理解したと信じているので、私自身の質問に答えます...
詳細が必要な場合は、調査結果をブログに投稿しました http://geekswithblogs.net/bbiales/archive/2012/03/15/how-to-nest-transactions-nicely---quotbegin-transactionquot -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
拡張 ブライアン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
このタイプのトランザクションマネージャーをストアドプロシージャで使用しました:
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