そのため、sp_SomeProcが無効なSQLステートメントを実行しようとすると、SQL Serverから次のエラーメッセージが表示されます。エラーが発生します:
The current transaction cannot be committed and cannot support operations that write to the log file.
私が間違っていることについてのアイデアはありますか?(これは問題を模倣するために作成したサンプルにすぎないので、「なぜこれを行っているのですか?」、「これはセキュリティに影響がある」などはありません。)
だから私のテーブルは次のようになります:
CREATE TABLE tSOMETABLE
(
RecID INT NOT NULL IDENTITY(1,1)
Val VARCHAR(20),
CONSTRAINT [PK_tSOMETABLE] PRIMARY KEY CLUSTERED
(
RecID ASC
)
)
だから私のトリガーでは:
CREATE TRIGGER [dbo].[TR_tSOMETABLE_INSERT]
ON [dbo].[tSOMETABLE]
FOR INSERT
AS
SET NOCOUNT ON
BEGIN
BEGIN
SELECT * INTO #temp FROM INSERTED
WHILE EXISTS (SELECT 1 FROM #temp)
BEGIN
DECLARE @RecID INT
SELECT @RecID = RecID
FROM #temp t
EXEC dbo.sp_SomeProc @EventType = 'ON INSERT', @RecID = @RecID
DELETE #temp WHERE @RecID = RecID
END
END
END
これで、sp_SomeProcのコードは次のようになります。
CREATE PROC sp_SomeProc
(
@EventType VARCHAR(50),
@RecID INT,
@Debug BIT = 0
)
AS
BEGIN
SET NOCOUNT ON
DECLARE @ProcTable TABLE
(
RecID INT NOT NULL IDENTITY(1,1),
Cmd VARCHAR(MAX)
)
INSERT INTO @ProcTable(Cmd)
SELECT 'EXEC sp_who'
UNION
SELECT 'EXEC sp_SomeStoredProcThatDoesntExist'
DECLARE @RecID INT
SELECT @RecID = MIN(RecID) FROM @ProcTable
WHILE @RecID IS NOT NULL
BEGIN
DECLARE @sql VARCHAR(MAX)
SELECT @sql = cmd FROM @ProcTable WHERE RecID = @RecID
IF @Debug = 1
PRINT @sql
ELSE
BEGIN
BEGIN TRY
EXEC(@sql)
END TRY
BEGIN CATCH
DECLARE @Msg VARCHAR(MAX), @ErrorNumber INT, @ErrorSeverity INT, @ErrorState int, @ErrorProcedure nvarchar(256), @ErrorLine int, @ErrorMessage nvarchar(MAX)
SELECT @Msg = 'Failed While Executing: ' + @sql
SELECT @ErrorNumber = ERROR_NUMBER(), @ErrorSeverity = ERROR_SEVERITY(), @ErrorState = ERROR_STATE(), @ErrorProcedure = ERROR_PROCEDURE(), @ErrorLine = ERROR_LINE(), @ErrorMessage = ERROR_MESSAGE()
-- DO SOME MORE STUFF HERE AND THEN ...
RAISERROR(@ErrorMessage, @ErrorSeverity, @ErrorState)
END CATCH
END
SELECT @RecID = MIN(RecID) FROM @ProcTable WHERE RecID > @RecID
END
END
だからテストするために私は試してみます:
INSERT INTO tSOMETABLE(Val)
SELECT 'Hello'
このエラーは、トランザクション内でtry/catchブロックを使用すると発生します。ささいな例を考えてみましょう:
SET XACT_ABORT ON
IF object_id('tempdb..#t') IS NOT NULL
DROP TABLE #t
CREATE TABLE #t (i INT NOT NULL PRIMARY KEY)
BEGIN TRAN
INSERT INTO #t (i) VALUES (1)
INSERT INTO #t (i) VALUES (2)
INSERT INTO #t (i) VALUES (3)
INSERT INTO #t (i) VALUES (1) -- dup key error, XACT_ABORT kills the batch
INSERT INTO #t (i) VALUES (4)
COMMIT TRAN
SELECT * FROM #t
4番目の挿入でエラーが発生すると、バッチが終了し、トランザクションがロールバックされます。今のところ驚きはありません。
次に、TRY/CATCHブロックでそのエラーを処理してみましょう。
SET XACT_ABORT ON
IF object_id('tempdb..#t') IS NOT NULL
DROP TABLE #t
CREATE TABLE #t (i INT NOT NULL PRIMARY KEY)
BEGIN TRAN
INSERT INTO #t (i) VALUES (1)
INSERT INTO #t (i) VALUES (2)
BEGIN TRY
INSERT INTO #t (i) VALUES (3)
INSERT INTO #t (i) VALUES (1) -- dup key error
END TRY
BEGIN CATCH
SELECT ERROR_MESSAGE()
END CATCH
INSERT INTO #t (i) VALUES (4)
/* Error the Current Transaction cannot be committed and
cannot support operations that write to the log file. Roll back the transaction. */
COMMIT TRAN
SELECT * FROM #t
重複キーのエラーをキャッチしましたが、それ以外の場合はうまくいきません。バッチは引き続き終了し、トランザクションは引き続きロールバックされます。その理由は、実際には非常に単純です。
TRY/CATCHブロックはトランザクションに影響しません。
XACT_ABORTがONになっているため、重複キーエラーが発生した瞬間に、トランザクションが終了します。それのために行われます。致命傷を負っています。それは心から撃たれました...そしてエラーは責任があります。 TRY/CATCHはSQL Serverに...悪い名前を与えます。 (すみません、抵抗できませんでした)
つまり、[〜#〜] never [〜#〜]コミットし、[〜#〜] always [〜#〜]はロールバックされます。 TRY/CATCHブロックでできることは、死体の落下を壊すことだけです。 XACT_STATE()関数を使用して、トランザクションがコミット可能かどうかを確認できます。そうでない場合、唯一のオプションはトランザクションをロールバックすることです。
SET XACT_ABORT ON -- Try with it OFF as well.
IF object_id('tempdb..#t') IS NOT NULL
DROP TABLE #t
CREATE TABLE #t (i INT NOT NULL PRIMARY KEY)
BEGIN TRAN
INSERT INTO #t (i) VALUES (1)
INSERT INTO #t (i) VALUES (2)
SAVE TRANSACTION Save1
BEGIN TRY
INSERT INTO #t (i) VALUES (3)
INSERT INTO #t (i) VALUES (1) -- dup key error
END TRY
BEGIN CATCH
SELECT ERROR_MESSAGE()
IF XACT_STATE() = -1 -- Transaction is doomed, Rollback everything.
ROLLBACK TRAN
IF XACT_STATE() = 1 --Transaction is commitable, we can rollback to a save point
ROLLBACK TRAN Save1
END CATCH
INSERT INTO #t (i) VALUES (4)
IF @@TRANCOUNT > 0
COMMIT TRAN
SELECT * FROM #t
トリガーは常にトランザクションのコンテキスト内で実行されるため、トリガー内でTRY/CATCHを使用しないようにすれば、処理ははるかに簡単になります。
問題の解決策として、CLR Stored Procを別の接続でSQL Serverに接続し、動的SQLを実行することができます。新しいトランザクションでコードを実行できるようになり、エラー処理ロジックはC#で簡単に記述および理解できます。