web-dev-qa-db-ja.com

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

内部に3つのストアドプロシージャのみを実行するストアドプロシージャがあります。マスターSPが成功した場合、1つのパラメーターのみを使用して保存します。

最初のストアドプロシージャがマスターストアドプロシージャで正常に機能し、2番目のストアドプロシージャが失敗した場合、マスター内のすべてのSPが自動的にロールバックされますSPまたは、何らかのコマンドを実行する必要がありますか?

これが私の手順です:

CREATE PROCEDURE [dbo].[spSavesomename] 
    -- Add the parameters for the stored procedure here

    @successful bit = null output
AS
BEGIN
begin transaction createSavebillinginvoice
    begin Try
    -- SET NOCOUNT ON added to prevent extra result sets from
    -- interfering with SELECT statements.
    SET NOCOUNT ON;

   BEGIN 

   EXEC [dbo].[spNewBilling1]

   END

   BEGIN 

   EXEC [dbo].[spNewBilling2]

   END

   BEGIN 

   EXEC [dbo].[spNewBilling3]

   END 

   set @successful  = 1

   end Try

    begin Catch
        rollback transaction createSavesomename
        insert into dbo.tblErrorMessage(spName, errorMessage, systemDate) 
             values ('spSavesomename', ERROR_MESSAGE(), getdate())

        return
    end Catch
commit transaction createSavesomename
return
END

GO
25
user2483342

質問に示されているコードのみが与えられ、3つのサブプロシージャのいずれにも明示的なトランザクション処理がないと仮定すると、はい、3つのサブプロシージャのいずれかでエラーがキャッチされ、ROLLBACKブロックのCATCHがロールしますすべての作業を元に戻します。

しかし、ここではトランザクションについて注意すべき点がいくつかあります(少なくともSQL Serverでは)。

  • _BEGIN TRAN_を何度呼び出しても、トランザクションは1つしかありませんrealトランザクション(最初のトランザクション)

    • トランザクションに名前を付けることができます(ここで行ったように)。その名前はログに表示されますが、名前を付けるのは最初/最も外側のトランザクションに対してのみ意味があります(最初のトランザクションはtheであるためトランザクション)。
    • _BEGIN TRAN_を呼び出すたびに、名前が付けられているかどうかにかかわらず、トランザクションカウンターが1ずつ増加します。
    • _SELECT @@TRANCOUNT;_を実行すると、現在のレベルを確認できます
    • _@@TRANCOUNT_が2以上のときに発行されたCOMMITコマンドは、トランザクションカウンターを1つずつ減らします。
    • _@@TRANCOUNT_が_1_にあるときにCOMMITが発行されるまで、何もコミットされません。
    • 上記の情報が明確に示されていない場合に備えて、トランザクションレベルに関係なく、トランザクションの実際の入れ子はありません。
  • セーブポイントを使用すると、取り消すことができるtheトランザクション内で作業のサブセットを作成できます。

    • セーブポイントは、_SAVE TRAN {save_point_name}_コマンドで作成/マークされます
    • セーブポイントは、トランザクション全体をロールバックせずに元に戻すことができる作業のサブセットのbeginningをマークします。
    • セーブポイント名は一意である必要はありませんが、同じ名前を複数回使用しても、異なるセーブポイントが作成されます。
    • 保存ポイントcanネストできます。
    • セーブポイントはコミットできません。
    • セーブポイントは、_ROLLBACK {save_point_name}_を使用して元に戻すことができます。 (これについては以下で詳しく説明します)
    • セーブポイントをロールバックすると、ロールバック中のセーブポイントが作成された後に作成されたセーブポイントを含むmostcent _SAVE TRAN {save_point_name}_の呼び出し後に発生したすべての作業が取り消されます(したがって、「ネスト」 )。
    • セーブポイントをロールバックしても、トランザクションの数/レベルには影響しません
    • 最初の_SAVE TRAN_より前に行われた作業は、トランザクション全体の完全なROLLBACKを発行しない限り、元に戻すことはできません。
    • 明確にするために、_@@TRANCOUNT_が2以上のときにCOMMITを発行しても、セーブポイントには影響しません(ここでも、1を超えるトランザクションレベルはそのカウンターの外に存在しないため)。
  • 特定の名前付きトランザクションをコミットすることはできません。トランザクション「name」は、COMMITとともに提供された場合、無視され、読みやすくするためにのみ存在します。

  • 名前なしで発行されたROLLBACKは、常にすべてのトランザクションをロールバックします。

  • 名前付きで発行されたROLLBACKは、次のいずれかに対応している必要があります。

    • 最初のトランザクションは、次の名前が付けられていると想定しています。
      同じトランザクション名で_SAVE TRAN_が呼び出されていないと仮定すると、すべてのトランザクションがロールバックされます。
    • 「セーブポイント」(上記):
      この動作は、最新 _SAVE TRAN {save_point_name}_が呼び出されてから行われたすべての変更を「取り消し」ます。
    • 最初のトランザクションがa)名前付きで、b)に_SAVE TRAN_コマンドがその名前で発行されている場合、そのトランザクション名の各ROLLBACKは、その名前がなくなるまで各セーブポイントを取り消します。その後、その名前で発行されたROLLBACKは、すべてのトランザクションをロールバックします。
    • たとえば、次のコマンドが示されている順序で実行されたとします。

      _BEGIN TRAN A -- @@TRANCOUNT is now 1
      -- DML Query 1
      SAVE TRAN A
      -- DML Query 2
      SAVE TRAN A
      -- DML Query 3
      
      BEGIN TRAN B -- @@TRANCOUNT is now 2
      SAVE TRAN B
      -- DML Query 4
      _

      ここで、発行する場合(以下の各シナリオは互いに独立しています):

      • _ROLLBACK TRAN B_一度:「DMLクエリ4」を取り消します。 _@@TRANCOUNT_は2のままです。
      • _ROLLBACK TRAN B_を2回:「B」に対応する保存ポイントがないため、「DMLクエリ4」を取り消してからエラーになります。 _@@TRANCOUNT_は2のままです。
      • _ROLLBACK TRAN A_一度:「DMLクエリ4」と「DMLクエリ3」を元に戻します。 _@@TRANCOUNT_は2のままです。
      • _ROLLBACK TRAN A_を2回:「DMLクエリ4」、「DMLクエリ3」、および「DMLクエリ2」を元に戻します。 _@@TRANCOUNT_は2のままです。
      • _ROLLBACK TRAN A_ thrice:「DMLクエリ4」、「DMLクエリ3」、「DMLクエリ2」を元に戻します。次に、トランザクション全体をロールバックします(残ったのは「DMLクエリ1」だけでした)。 _@@TRANCOUNT_が0になりました。
      • COMMIT 1回:_@@TRANCOUNT_が1に下がります。
      • COMMITが1回、次に_ROLLBACK TRAN B_が1回:_@@TRANCOUNT_が1に下がります。その後、「DMLクエリ4」が取り消されます(COMMITが何も行わなかった場合)。 _@@TRANCOUNT_はまだ1です。
  • トランザクション名とセーブポイント名:

    • 32文字まで入力できます
    • インスタンスレベルまたはデータベースレベルの照合に関係なく、バイナリ照合(現在のドキュメントでは大文字と小文字は区別されません)を持つものとして扱われます。
    • 詳細については、次の投稿のトランザクション名セクションを参照してください: What's in a Name ?: Inside the Wacky World of T-SQL識別子
  • ストアドプロシージャ自体は、暗黙的なトランザクションではありません。各query明示的なトランザクションが開始されていない場合は、暗黙的なトランザクションです。これが、ROLLBACKを実行するプログラム上の理由がない限り、単一のクエリに関する明示的なトランザクションが必要ない理由です。そうでない場合、クエリのエラーはそのクエリの自動ロールバックです。

  • ストアドプロシージャを呼び出す場合、_@@TRANCOUNT_の値は、呼び出されたときと同じで終了する必要があります。つまり、次のことはできません。

    • 呼び出し側/親プロセスでコミットすることを期待して、コミットせずに_BEGIN TRAN_をprocで開始します。
    • Procが呼び出される前に明示的なトランザクションが開始された場合、_@@TRANCOUNT_を0に戻すため、ROLLBACKを発行できません。

    開始時よりも多いまたは少ないトランザクション数でストアドプロシージャを終了すると、次のようなエラーが発生します。

    メッセージ266、レベル16、状態2、プロシージャYourProcName、行0
    EXECUTE後のトランザクション数は、BEGINステートメントとCOMMITステートメントの数が一致しないことを示しています。以前のカウント= X、現在のカウント=Y。

  • 通常の変数と同様に、テーブル変数はトランザクションに拘束されません。


独立して呼び出すことができる(したがってトランザクション処理が必要)、または他のプロシージャから呼び出すことができる(したがってトランザクション処理を必要としない)procでトランザクション処理を行うことについて:これはいくつかの方法で実行できます。

私が数年間これを扱ってきた方法がうまくいくように見えるのは、最外層のBEGIN/COMMIT/ROLLBACKのみです。サブプロシージャコールは、トランザクションコマンドをスキップするだけです。以下に、各proc(トランザクション処理を必要とする各proc)に何を入れたかを概説しました。

  • 各プロシージャの上部にある_DECLARE @InNestedTransaction BIT;_
  • 単純な_BEGIN TRAN_の代わりに、以下を実行します。

    _IF (@@TRANCOUNT = 0)
    BEGIN
       SET @InNestedTransaction = 0;
       BEGIN TRAN; -- only start a transaction if not already in one
    END;
    ELSE
    BEGIN
       SET @InNestedTransaction = 1;
    END;
    _
  • 単純なCOMMITの代わりに、以下を実行します。

    _IF (@@TRANCOUNT > 0 AND @InNestedTransaction = 0)
    BEGIN
       COMMIT;
    END;
    _
  • 単純なROLLBACKの代わりに、以下を実行します。

    _IF (@@TRANCOUNT > 0 AND @InNestedTransaction = 0)
    BEGIN
       ROLLBACK;
    END;
    _

このメソッドは、トランザクションがSQL Server内で開始されたか、アプリレイヤーで開始されたかに関係なく、同じように機能します。

_TRY...CATCH_構造内でのこのトランザクション処理の完全なテンプレートについては、次のDBA.SEの質問に対する私の回答を参照してください。 C#コードおよびストアドプロシージャでトランザクションを処理する必要があります =。


「基本」を超えて、トランザクションには次のようなニュアンスがあります。

  • デフォルトでは、トランザクションは、ほとんどの場合、エラーが発生したときに自動的にロールバック/キャンセルされません。適切なエラー処理があり、ROLLBACKを自分で呼び出す限り、これは通常問題になりません。ただし、バッチ中止エラーの場合、またはOPENQUERY(または一般にリンクサーバー)を使用する場合など、状況が複雑になり、リモートシステムでエラーが発生することがあります。ほとんどのエラーは_TRY...CATCH_を使用してトラップできますが、その方法でトラップできないエラーが2つあります(ただし、現時点ではエラーを覚えていません-調査中)。これらの場合、トランザクションを適切にロールバックするには、_SET XACT_ABORT ON_を使用する必要があります。

    SET XACT_ABORT ON SQL Serverを実行させます即時任意のトランザクションをロールバックします(アクティブな場合)andバッチを中止しますanyエラーが発生します。この設定は、_TRY...CATCH_構成を導入したSQL Server 2005より前に存在していました。ほとんどの場合、_TRY...CATCH_はほとんどの状況を処理するので、_XACT_ABORT ON_の必要性はほとんどなくなります。ただし、OPENQUERYを使用している場合(および、現時点では覚えていない可能性がある他のシナリオの1つ)、引き続き_SET XACT_ABORT ON;_を使用する必要があります。

  • トリガー内では、_XACT_ABORT_は暗黙的にONに設定されます。これにより、トリガー内でanyエラーが発生し、トリガーを起動したDMLステートメント全体がキャンセルされます。

  • 特にトランザクションを使用する場合は、常に適切なエラー処理が必要です。 SQL Server 2005で導入された_TRY...CATCH_構文は、ほぼすべての状況を処理する手段を提供します。各ステートメントの後の_@@ERROR_のテストに対する歓迎すべき改善であり、バッチ中止エラーにはあまり役立ちませんでした。

    ただし、_TRY...CATCH_では新しい「状態」が導入されました。 _TRY...CATCH_構文を使用してnotを実行しているときに、アクティブなトランザクションがあり、エラーが発生した場合、いくつかのパスをとることができます。

    • _XACT_ABORT OFF_およびステートメント中止エラー:トランザクションはまだアクティブであり、処理は次のステートメントがあればそれから続行されます。
    • _XACT_ABORT OFF_とほとんどのバッチ中止エラー:トランザクションはまだアクティブであり、次のbatchがあれば処理が続行されます。
    • _XACT_ABORT OFF_および特定のバッチ中止エラー:トランザクションはロールバックされ、処理は次のbatchがあればそれから続行されます。
    • _XACT_ABORT ON_およびanyエラー:トランザクションはロールバックされ、処理は次のbatchがあれば続行されます。


    ただし、_TRY...CATCH_を使用する場合、バッチ中止エラーはバッチを中止せず、代わりに制御をCATCHブロックに転送します。 _XACT_ABORT_がOFFである場合、トランザクションは大部分の時間アクティブであり、COMMIT、またはおそらくROLLBACKが必要になります。ただし、特定のバッチ中止エラー(OPENQUERYなど)が発生した場合、または_XACT_ABORT_がONである場合、トランザクションは新しい状態「コミット不可」になります。この状態では、COMMITを実行することも、DML操作を実行することもできません。実行できるのは、ROLLBACKステートメントとSELECTステートメントだけです。ただし、この「不可能な」状態では、トランザクションはエラーの発生時にロールバックされ、ROLLBACKの発行は形式的なものですが、実行する必要があります。

    関数 XACT_STATE を使用して、トランザクションがアクティブであるか、コミットできないか、または存在しないかを判別できます。 _-1_かどうかをテストするのではなく、CATCHブロックでこの関数をチェックして、結果が_@@TRANCOUNT > 0_(つまり、コミットできない)かどうかを判断することをお勧めします。しかし、_XACT_ABORT ON_では、それが可能な唯一の状態であるべきなので、_@@TRANCOUNT > 0_とXACT_STATE() <> 0のテストは同等であるようです。一方、_XACT_ABORT_がOFFであり、アクティブなトランザクションがある場合、CATCHブロックで_1_または_-1_のいずれかの状態になる可能性があり、これにより、 COMMITの代わりにROLLBACKを発行する可能性があります(ただし、トランザクションがコミット可能な場合に誰かがCOMMITを使用したい場合については考えられません)。 _XACT_ABORT ON_を指定したCATCHブロック内でのXACT_STATE()の使用に関する詳細と調査は、次のDBA.SEの質問に対する私の回答にあります。 トランザクションをコミットできるケースXACT_ABORTがONに設定されている場合のCATCHブロック内)XACT_STATE()には、特定のシナリオで誤って_1_を返すマイナーなバグがあることに注意してください。 XACT_STATE()は、一部のシステム変数を使用してSELECTで使用すると1を返しますFROM句


元のコードに関するメモ:

  • トランザクションに付与された名前は何の役にも立たないので、削除することができます。
  • BEGINの呼び出しごとにENDEXECは必要ありません
58
Solomon Rutzky

はい。マスターストアドプロシージャのcatchステートメントのエラーロールバックコードが実行されると、直接ステートメントによって、またはその中にあるネストされたストアドプロシージャによって実行されたすべての操作がロールバックされます。

ネストされたストアドプロシージャで明示的なトランザクションを適用していない場合でも、これらのストアドプロシージャは暗黙的なトランザクションを使用し、完了時にコミットしますが、ネストされたストアドプロシージャで明示的または暗黙的なトランザクションを介してコミットした場合、SQL Serverエンジンはそれを無視します。マスターストアドプロシージャが失敗し、トランザクションがロールバックされた場合、これらのネストされたストアドプロシージャによるすべてのアクションをロールバックします。

トランザクションは、最も外側のトランザクションの最後に行われたアクションに基づいてコミットまたはロールバックされるたびに。外側のトランザクションがコミットされると、内側のネストされたトランザクションもコミットされます。外部トランザクションがロールバックされると、内部トランザクションが個別にコミットされたかどうかに関係なく、すべての内部トランザクションもロールバックされます。

参考のために http://technet.Microsoft.com/en-us/library/ms189336(v = sql.105).aspx

2
aasim.abdullah