sql >> データベース >  >> RDS >> Sqlserver

SQLServerのネストされたトランザクションでエラーを処理する方法

    この記事では、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

    結論

    この記事では、ネストされたトランザクションを調査し、このタイプのクエリでエラーを処理する方法を分析しました。このトランザクションタイプに関する最も重要なルールは、外部トランザクションまたは内部トランザクションでエラーが発生する可能性があるため、防御クエリを作成することです。このため、クエリのエラー処理動作を設計する必要があります。

    参考資料

    ネストトランザクション

    トランザクションを保存


    1. PostgreSQL:psqlコマンドラインユーティリティを使用する場合のWindowsでのエンコーディングの問題

    2. MariaDBでのUPPER()のしくみ

    3. MySQLのカンマ区切り文字列を一時テーブルに分割

    4. SQLServer結果セットの行を制限する方法