内部に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
質問に示されているコードのみが与えられ、3つのサブプロシージャのいずれにも明示的なトランザクション処理がないと仮定すると、はい、3つのサブプロシージャのいずれかでエラーがキャッチされ、ROLLBACK
ブロックのCATCH
がロールしますすべての作業を元に戻します。
しかし、ここではトランザクションについて注意すべき点がいくつかあります(少なくともSQL Serverでは)。
_BEGIN TRAN
_を何度呼び出しても、トランザクションは1つしかありませんrealトランザクション(最初のトランザクション)
BEGIN TRAN
_を呼び出すたびに、名前が付けられているかどうかにかかわらず、トランザクションカウンターが1ずつ増加します。SELECT @@TRANCOUNT;
_を実行すると、現在のレベルを確認できます@@TRANCOUNT
_が2以上のときに発行されたCOMMIT
コマンドは、トランザクションカウンターを1つずつ減らします。@@TRANCOUNT
_が_1
_にあるときにCOMMIT
が発行されるまで、何もコミットされません。セーブポイントを使用すると、取り消すことができるtheトランザクション内で作業のサブセットを作成できます。
SAVE TRAN {save_point_name}
_コマンドで作成/マークされますROLLBACK {save_point_name}
_を使用して元に戻すことができます。 (これについては以下で詳しく説明します)SAVE TRAN {save_point_name}
_の呼び出し後に発生したすべての作業が取り消されます(したがって、「ネスト」 )。SAVE TRAN
_より前に行われた作業は、トランザクション全体の完全なROLLBACK
を発行しない限り、元に戻すことはできません。@@TRANCOUNT
_が2以上のときにCOMMIT
を発行しても、セーブポイントには影響しません(ここでも、1を超えるトランザクションレベルはそのカウンターの外に存在しないため)。特定の名前付きトランザクションをコミットすることはできません。トランザクション「name」は、COMMIT
とともに提供された場合、無視され、読みやすくするためにのみ存在します。
名前なしで発行されたROLLBACK
は、常にすべてのトランザクションをロールバックします。
名前付きで発行されたROLLBACK
は、次のいずれかに対応している必要があります。
SAVE TRAN
_が呼び出されていないと仮定すると、すべてのトランザクションがロールバックされます。SAVE TRAN {save_point_name}
_が呼び出されてから行われたすべての変更を「取り消し」ます。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です。トランザクション名とセーブポイント名:
ストアドプロシージャ自体は、暗黙的なトランザクションではありません。各query明示的なトランザクションが開始されていない場合は、暗黙的なトランザクションです。これが、ROLLBACK
を実行するプログラム上の理由がない限り、単一のクエリに関する明示的なトランザクションが必要ない理由です。そうでない場合、クエリのエラーはそのクエリの自動ロールバックです。
ストアドプロシージャを呼び出す場合、_@@TRANCOUNT
_の値は、呼び出されたときと同じで終了する必要があります。つまり、次のことはできません。
BEGIN TRAN
_を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
の呼び出しごとにEND
とEXEC
は必要ありませんはい。マスターストアドプロシージャのcatchステートメントのエラーロールバックコードが実行されると、直接ステートメントによって、またはその中にあるネストされたストアドプロシージャによって実行されたすべての操作がロールバックされます。
ネストされたストアドプロシージャで明示的なトランザクションを適用していない場合でも、これらのストアドプロシージャは暗黙的なトランザクションを使用し、完了時にコミットしますが、ネストされたストアドプロシージャで明示的または暗黙的なトランザクションを介してコミットした場合、SQL Serverエンジンはそれを無視します。マスターストアドプロシージャが失敗し、トランザクションがロールバックされた場合、これらのネストされたストアドプロシージャによるすべてのアクションをロールバックします。
トランザクションは、最も外側のトランザクションの最後に行われたアクションに基づいてコミットまたはロールバックされるたびに。外側のトランザクションがコミットされると、内側のネストされたトランザクションもコミットされます。外部トランザクションがロールバックされると、内部トランザクションが個別にコミットされたかどうかに関係なく、すべての内部トランザクションもロールバックされます。
参考のために http://technet.Microsoft.com/en-us/library/ms189336(v = sql.105).aspx