この記事では、1つまたは複数のトランザクションを含むトランザクションブロックであるSQLServerのネストされたトランザクションについて説明します。
この画像は、ネストされたトランザクションの単純なモデルを示しています。
内部トランザクションは、トランザクションブロックで構成されるストアドプロシージャです。 MSDNは、最初のアプローチとはまったく逆の「トランザクションをできるだけ短くする」ことを推奨しています。私の意見では、ネストされたトランザクションの使用はお勧めしません。それでも、ビジネス上の問題を解決するためにそれらを使用しなければならない場合があります。
したがって、次のことを理解します。
- 外部トランザクションがロールバックまたはコミットされるとどうなりますか?
- 内部トランザクションがロールバックまたはコミットされるとどうなりますか?
- ネストされたトランザクションエラーを処理する方法は?
まず、デモテーブルを作成し、考えられるケースをテストします。
USE AdventureWorks -----Create Demo Table---- CREATE TABLE CodingSightDemo (NumberValue VARCHAR(20))
ケース1:外部トランザクションと内部トランザクションの両方がコミットされます。
TRUNCATE TABLE CodingSightDemo --<*************OUTHER TRANSACTION START*************> BEGIN TRAN INSERT INTO CodingSightDemo VALUES('One') --<INNER TRANSACTION START> BEGIN TRAN INSERT INTO CodingSightDemo VALUES('Two') COMMIT TRAN --< INNER TRANSACTION END> INSERT INTO CodingSightDemo VALUES('Three') COMMIT TRAN --<************* OUTHER TRANSACTION END*************> SELECT * FROM CodingSightDemo
この場合、すべてのレコードがテーブルに正常に挿入されています。すべてのINSERTステートメントがエラーを返さないことを前提としています。
ケース2:外部トランザクションがロールバックされる 、内部トランザクションはコミット 。
TRUNCATE TABLE CodingSightDemo --<*************OUTHER TRANSACTION START*************> BEGIN TRAN INSERT INTO CodingSightDemo VALUES('One') --<INNER TRANSACTION START> BEGIN TRAN INSERT INTO CodingSightDemo VALUES('Two') COMMIT TRAN --< INNER TRANSACTION END> INSERT INTO CodingSightDemo VALUES('Three') rollback TRAN --<************* OUTHER TRANSACTION END*************> SELECT * FROM CodingSightDemo
ご覧のとおり、内部トランザクションは外部トランザクションの一部であるため、レコードはテーブルに挿入されません。このため、内部トランザクションはロールバックされます。
ケース3:外部トランザクションがコミットされている 、内部トランザクションはロールバックされます 。
TRUNCATE TABLE CodingSightDemo --<*************OUTHER TRANSACTION START*************> BEGIN TRAN INSERT INTO CodingSightDemo VALUES('One') --<INNER TRANSACTION START> BEGIN TRAN INSERT INTO CodingSightDemo VALUES('Two') ROLLBACK TRAN --< INNER TRANSACTION END> INSERT INTO CodingSightDemo VALUES('Three') COMMIT TRAN --<************* OUTHER TRANSACTION END*************> SELECT * FROM CodingSightDemo
この場合、エラーが発生し、最新のステートメントがテーブルに挿入されました。その結果、いくつかの疑問が生じます:
- エラーが発生したのはなぜですか?
- 最新のINSERTステートメントがテーブルに追加されたのはなぜですか?
原則として、ROLLBACK TRANステートメントは、現在のセッションで実行されたすべての開いているトランザクションをロールバックします。エラーが返されるため、クエリを作成できません。
BEGIN TRAN INSERT INTO CodingSightDemo VALUES('One') BEGIN TRAN INSERT INTO CodingSightDemo VALUES('Two') ROLLBACK TRAN ROLLBACK TRAN
このルールが私たちのケースにどのように影響するかを調べます。 ROLLBACK TRANステートメントは、内部トランザクションと外部トランザクションをロールバックします。このため、開いているトランザクションがないため、COMMITTRANステートメントを実行するとエラーが発生します。
次に、このクエリにエラー処理ステートメントを追加し、防御プログラミングアプローチに基づいて変更します(ウィキペディアが述べているように、防御プログラミングは、予期しない状況下でソフトウェアの一部の継続的な機能を保証することを目的とした防御設計の形式です)。エラー処理を気にせずにクエリを記述してエラーが発生すると、データの整合性が損なわれる可能性があります。
次のスクリプトでは、セーブポイントを使用します。これらはトランザクション内のポイントをマークし、必要に応じて、すべてのDML(データ操作言語)ステートメントをマークされたポイントにロールバックできます。
BEGIN TRY BEGIN TRAN INSERT INTO CodingSightDemo VALUES('One') --<INNER TRANSACTION START> SAVE TRANSACTION innerTRAN BEGIN TRY BEGIN TRAN INSERT INTO CodingSightDemo VALUES('Two') COMMIT TRAN END TRY BEGIN CATCH IF XACT_STATE() <> 0 BEGIN ROLLBACK TRANSACTION innerTRAN PRINT 'Roll back occurs for inner tran' END IF XACT_STATE() <> 0 BEGIN COMMIT TRAN PRINT 'Commit occurs for firt open tran' END END CATCH --< INNER TRANSACTION END> INSERT INTO CodingSightDemo VALUES('Three') COMMIT TRAN END TRY BEGIN CATCH BEGIN IF XACT_STATE() <> 0 ROLLBACK TRAN PRINT 'Roll back occurs for outer tran' END END CATCH --<************* OUTHER TRANSACTION END*************> SELECT * FROM CodingSightDemo
このクエリは、内部トランザクションでエラーが発生したときにエラーを処理します。また、外部トランザクションは正常にコミットされます。ただし、場合によっては、内部トランザクションでエラーが発生した場合、外部トランザクションをロールバックする必要があります。この場合、内部クエリのエラー状態値を保持して渡すローカル変数を使用します。この変数値を使用して外部クエリを設計します。クエリは次のようになります。
--<*************OUTHER TRANSACTION START*************> DECLARE @innertranerror as int=0 BEGIN TRY BEGIN TRAN INSERT INTO CodingSightDemo VALUES('One') --<INNER TRANSACTION START> SAVE TRANSACTION innerTRAN BEGIN TRY BEGIN TRAN INSERT INTO CodingSightDemo VALUES('Two') COMMIT TRAN END TRY BEGIN CATCH IF XACT_STATE() <> 0 BEGIN SET @innertranerror=1 ROLLBACK TRANSACTION innerTRAN PRINT 'Roll back occurs for inner tran' END IF XACT_STATE() <> 0 BEGIN COMMIT TRAN PRINT 'Commit occurs for firt open tran' END END CATCH --< INNER TRANSACTION END> INSERT INTO CodingSightDemo VALUES('Three') if @innertranerror=0 BEGIN COMMIT TRAN END IF @innertranerror=1 BEGIN ROLLBACK TRAN END END TRY BEGIN CATCH BEGIN IF XACT_STATE() <> 0 ROLLBACK TRAN PRINT 'Roll back occurs for outer tran' END END CATCH --<************* OUTHER TRANSACTION END*************> SELECT * FROM CodingSightDemo
結論
この記事では、ネストされたトランザクションを調査し、このタイプのクエリでエラーを処理する方法を分析しました。このトランザクションタイプに関する最も重要なルールは、外部トランザクションまたは内部トランザクションでエラーが発生する可能性があるため、防御クエリを作成することです。このため、クエリのエラー処理動作を設計する必要があります。
参考資料
ネストトランザクション
トランザクションを保存