テーブルの挿入トリガーが失敗したときのデータ損失を回避しようとしています。次のシナリオでこれを試していますが、コードが失敗します。
Customersテーブルで挿入が発生した場合、同じデータをArchiveテーブルに挿入します。トリガーが失敗した場合、トランザクション全体をロールバックしたくありません。これはCustomersテーブルのデータ損失につながります。
トリガーが失敗した場合でも、データをCustomersテーブルに挿入し、ストアドプロシージャは通常どおりCustomer_IDを返す必要があります。
ALTER TRIGGER [dbo].[Customer_Insert_Trigger_Test]
ON [dbo].[Customers]
AFTER INSERT
AS
BEGIN
BEGIN TRY
begin transaction;
set nocount on;
SAVE TRANSACTION InsertSaveHere;
--Simulating error situation
RAISERROR (N'This is message %s %d.', -- Message text.
11, -- Severity,
1, -- State,
N'number', -- First argument.
5); -- Second argument.
Insert into Archive select * from Inserted;
commit transaction;
END TRY
BEGIN CATCH
ROLLBACK TRANSACTION InsertSaveHere;
END CATCH
END
私の質問は主に、トリガーが失敗した場合に、customersテーブルに実際に挿入してロールバックしないようにする方法に関するものです。そのために私のコードを変更するには?
質問は「コードが失敗している」と述べていますが、エラーメッセージや具体的に何が失敗しているのかを示すものはありません。これらの情報の両方ではなくても、少なくとも1つを含めることは、常により良い答えを得るのに役立ちます。
とりあえず、トリガーとトランザクションについて誤った仮定のように見えるものがあります。@@TRANCOUNT
を呼び出してBEGIN TRAN;
をインクリメントしますが、@@TRANCOUNT
行がTRY
ブロック内で実行され、エラーがない場合にのみCOMMIT TRAN;
をデクリメントします。エラーの場合、COMMIT
はスキップされ、セーブポイントのROLLBACK
が発生します。ただし、セーブポイントをロールバックしても、@@TRANCOUNT
は減りません。この場合、INSERT
操作は終了し、トランザクションはまだアクティブです。
トリガーは、トリガーを起動したDML操作にバインドする、内部で開始されたトランザクション内に存在します。これは、トリガー内でROLLBACK
を呼び出して、そのDML操作をキャンセルする方法です。
これを念頭に置いて、これを機能させるために、BEGIN TRAN;
およびCOMMIT TRAN;
行を削除できるはずです。正味の効果は、エラーがない場合、INSERT
からArchive
テーブルへの期待どおりのコミットですが、エラーがある場合は、ROLLBACK
を保存ポイントまで実行して続行します。
ただし、これら2つの部分を削除しても、次のエラーが発生するという厄介な状況が残ります。
メッセージ3931、レベル16、状態1、手順Customer_Insert_Trigger_Test、行78
現在のトランザクションはコミットできず、セーブポイントにロールバックできません。トランザクション全体をロールバックします。
この動作の理由は、トリガーを呼び出すときにシステムがXACT_ABORT ON
を暗黙的に設定しているためです。 XACT_ABORT ON
の効果は、anyエラーが発生した場合にトランザクションをキャンセルすることです。治療法は?トリガーの先頭にXACT_ABORT OFF
を設定するだけです。
たとえば、以下は私にとってはうまくいきます:
CREATE
--ALTER
TRIGGER [dbo].[Customer_Insert_Trigger_Test]
ON [dbo].[Inserts]
AFTER INSERT
AS
BEGIN
SET NOCOUNT ON;
SET XACT_ABORT OFF;
BEGIN TRY
PRINT '@@TRANCOUNT = ' + CONVERT(VARCHAR(10), @@TRANCOUNT); -- for debug only
SAVE TRANSACTION InsertSaveHere;
--Simulating error situation
RAISERROR (N'This is message %s %d.', -- Message text.
11, -- Severity,
1, -- State,
N'number', -- First argument.
5); -- Second argument.
--Insert into Archive select * from Inserted;
END TRY
BEGIN CATCH
PRINT 'Entering CATCH block...'; -- for debug only
ROLLBACK TRANSACTION InsertSaveHere;
END CATCH;
END;
このメソッドはnotを実行することに注意してください==このテーブルでのトリガーの予想される動作を変更します:1)最も外側のレイヤーで発生する実際のCOMMIT
(最初のDMLステートメントまたはそのステートメントの前に明示的なトランザクションが開始されていた場合はそれ以上)、2)このテーブルの他の潜在的なトリガーがROLLBACKを発行して操作をキャンセルできないこと、および3)DMLの前に開始された明示的なトランザクションの機能このテーブルのステートメント。ROLLBACKを発行して、このテーブルのDML操作を含むすべての変更をキャンセルします。
もちろん、ここでエラーが発生する可能性があるのがINSERT
テーブルへのArchive
だけである場合は、SAVE TRAN
とROLLBACK TRANSACTION InsertSaveHere;
を削除し、CATCH
ブロックで何かを実行して、DECLARE @Test INT;
のように空にならないようにすることもできます。うまくいくかもしれません。ここでの推論は、エラーが実際に発生したことのない単一のDMLステートメントであるため、ロールバックするものは何もないということです;-)。
タイトルに記載されている質問に答えるには、トリガー内でCOMMIT
を使用できるようにする必要がありますが、私はeXtreeemely予想されるものを変更するので、そのようなことをするのに慎重ですトランザクションがコミットまたはロールバックするときの動作。これにより、このテーブルの他のトリガーの適切な操作が妨げられる可能性があります(このトリガーが最初に実行されると、ROLLBACK
を発行して操作をキャンセルできなくなります)。このテーブルでのDML操作の前に明示的なトランザクションが開始されました。
これを行うには(注:この段落を読み続ける前に、すぐ上の段落を読む必要があります)、COMMIT TRAN;
を発行します(トリガーがトランザクション内に既に存在するため)。 BEGIN TRAN;
。 COMMIT TRAN;
は最初のDML操作をコミットし、BEGIN TRAN;
は@@TRANCOUNT
を1に戻して、トリガーの実行が終了したときに、トリガーが開始時とは異なる@@TRANCOUNT
で終了したことを示すエラーが発生しないようにします。
トリガーはすでに暗黙のトランザクション内で常に動作しています。関連する質問を参照してください:
SQL Serverトリガーが実行されることを確認する方法はありますか?
エラーをキャッチして中止/ロールバックを防ぐことができますが、トリガー内からトランザクションを「コミット」することはできません。