はじめに
こんにちは。カート決済部の林です。ZOZOTOWN内のカートや決済の機能開発、保守運用を担当しています。
過去に福袋販売イベントの負荷対策の記事を掲載しました。
上記の記事では、タイムアウトしたプロセスがロックを掴んだままになっていたことが原因で、大量のブロッキングが発生していました。詳細な負荷や対策の内容について知りたい方は、ぜひ上記の記事を読んでみてください。
こちらの原因を解決するために、XACT_ABORT
の設定をON
にすることが有効であると記載しています。XACT_ABORT
はトランザクション内でエラーが発生すると即座にロールバック+ロックの解放を指示できるオプションです。このオプションをON
にすることで、タイムアウトした時点でロックが解放され、ブロッキングが発生しなくなりました。
ただし、設定をON
に変えると一部動作が変わり、既存の処理が正常に動かなくなることがあります。弊社でON
にした際も一部のストアドプロシージャ(以下ストアド)が正常に動かなくなりました。その時にXACT_ABORT
の動作について確認したので、その確認内容について本記事で紹介したいと思います。
XACT_ABORT
の動作について知りたい方や、これからON
にしようとしている方などの参考になれば幸いです。
目次
XACT_ABORTの概要
XACT_ABORT
はSQL Serverのオプションの1つで、デフォルトではOFF
になっています。
ON/OFF
それぞれの動作を以下の表に記載します。
設定 | エラー時の動作 | 後続の処理 |
---|---|---|
OFF | 発生した処理のみがロールバック | 実行される場合がある |
ON | トランザクション全体が終了しロールバック | 実行されない |
実際に上記の動作についてテスト用のテーブルを作成し動作確認を行います。
動作確認の準備
実際に動作を確認するために、テスト用のテーブルと初期レコードを準備します。テーブル作成は以下のDDLを実行し、table1
とtable2
を作成します。このとき、動作確認時にエラーを起こしやすくするために、table1
のcol1
とtable2
のcol1
に外部キー制約を設定します。
CREATE TABLE table1 (col1 INT NOT NULL PRIMARY KEY); CREATE TABLE table2 (col1 INT NOT NULL REFERENCES table1(col1));
初期のレコードとして以下のクエリを実行しtable1
に4レコードINSERT
します。
INSERT INTO table1 VALUES (1); INSERT INTO table1 VALUES (3); INSERT INTO table1 VALUES (4); INSERT INTO table1 VALUES (5);
ここまでで準備完了になります。
トランザクション内での動作
XACT_ABORT
がOFFの場合
動作確認のために以下のクエリを実行します。
SET XACT_ABORT OFF; GO BEGIN TRANSACTION; INSERT INTO table2 VALUES (1); INSERT INTO table2 VALUES (2); -- 外部キー制約によりエラーになる INSERT INTO table2 VALUES (3); COMMIT TRANSACTION; GO
1行目でXACT_ABORT
をOFF
に設定します。その後トランザクションを開始します。トランザクション内でtable2
に3回のINSERT
を行います。1回目と3回目のINSERT
は正常に完了します。しかし、2回目のINSERT
ではtable1
のcol1
に2が入っているレコードが存在しないため、外部キー制約により失敗します。3回のINSERT
が終わった後にCOMMIT TRANSACTION
を行います。
では、この時にtable2
のレコードはどうなっているでしょうか。以下のクエリで見てみます。
SELECT * FROM table2
クエリの結果は以下になります。
2回目のINSERT
でエラーになっているのですが、1,3回目のINSERT
は反映されています。つまりは、XACT_ABORT
がOFF
の場合には一部のクエリがエラーになってもそのまま処理が続けられます。なので、エラーハンドリングを自前で行う必要があります。
例えば今回の場合に、2回目のINSERT
が失敗したら全てロールバックするには以下のようにTRY-CATCH
で囲います。
SET XACT_ABORT OFF; GO BEGIN TRY BEGIN TRANSACTION; INSERT INTO table2 VALUES (1); INSERT INTO table2 VALUES (2); -- 外部キー制約によりエラーになる INSERT INTO table2 VALUES (3); COMMIT TRANSACTION; END TRY BEGIN CATCH ROLLBACK TRANSACTION; END CATCH GO
XACT_ABORT
がONの場合
では次にON
の場合の動作を見ていきます。OFF
の動作確認時のtable2
のレコードを以下のクエリで削除します。
DELETE table2
以下のクエリで動作確認をします。
SET XACT_ABORT ON; GO BEGIN TRANSACTION; INSERT INTO table2 VALUES (1); INSERT INTO table2 VALUES (2); -- 外部キー制約によりエラーになる INSERT INTO table2 VALUES (3); COMMIT TRANSACTION; GO
クエリの内容としては1行目でXACT_ABORT
をON
にした以外はOFF
の動作確認と同じです。このクエリを実行後に以下のクエリでtable2
のレコードを見ます。
SELECT * FROM table2
取得できたレコードの件数は0件になります。つまりは、XACT_ABORT
がON
の場合にエラーが発生すると、その時点で処理が終わりロールバックされます。なので、ON
の場合に自前でロールバックを行う必要はありません。
ストアド内での動作
XACT_ABORT
がOFFの場合
XACT_ABORT
がOFF
で動作するストアドを作成します。
CREATE PROCEDURE TEST_PROC AS SET XACT_ABORT OFF; BEGIN TRANSACTION; INSERT INTO table2 VALUES (2); -- 外部キー制約によりエラーになる IF @@ERROR = 0 BEGIN COMMIT TRANSACTION; RETURN 0 END ELSE BEGIN ROLLBACK TRANSACTION; RETURN 1 END GO
table2
へのINSERT
の結果により返り値を変えています。本記事の環境ではtable1
のcol1
に2
が入っているレコードが存在しないため、外部キー制約によるエラーになります。そのため、返り値は1となりロールバックされます。
作成したストアドを以下のクエリで実行します。
DECLARE @Return INT EXEC @Return = TEST_PROC SELECT 'Return Value' = @Return GO
すると以下の結果になります。
XACT_ABORT
がONの場合
では次にXACT_ABORT
の設定をON
に変えます。
ALTER PROCEDURE TEST_PROC AS SET XACT_ABORT ON; BEGIN TRANSACTION; INSERT INTO table2 VALUES (2); -- 外部キー制約によりエラーになる IF @@ERROR = 0 BEGIN COMMIT TRANSACTION; RETURN 0 END ELSE BEGIN ROLLBACK TRANSACTION; RETURN 1 END GO
変更点は3行目のXACT_ABORT
の設定をON
に切り替えたところのみです。変更後のストアドを以下のクエリで実行します。
DECLARE @Return INT EXEC @Return = TEST_PROC SELECT 'Return Value' = @Return GO
Return Value
が返ってこなくなります。これはXACT_ABORT
の設定により処理が中断されたためです。返り値で1を返せるようにするには、@@ERROR
でハンドリングしていたところをTRY-CATCH
にする必要があります。TRY-CATCH
に修正したストアドが以下になります。
ALTER PROCEDURE TEST_PROC AS SET XACT_ABORT ON; BEGIN TRANSACTION; BEGIN TRY INSERT INTO table2 VALUES (2); -- 外部キー制約によりエラーになる END TRY BEGIN CATCH ROLLBACK TRANSACTION; RETURN 1 END CATCH COMMIT TRANSACTION; RETURN 0 GO
変更後のストアドを以下のクエリで実行します。
DECLARE @Return INT EXEC @Return = TEST_PROC SELECT 'Return Value' = @Return GO
結果は以下のようになり、XACT_ABORT
がOFF
の時と同じように返り値が1
になることが確認できます。
このように、@@ERROR
でハンドリングしている処理がある場合には修正が必要になります。
弊社では@@ERROR
でハンドリングしているストアドがあり、ON
にするタイミングで@@ERROR
のハンドリングからTRY-CATCH
を使ったハンドリングへ修正しました。
注意点
本記事ではSQL Server Management Studioからクエリを実行してXACT_ABORT
の動作を確認しています。そのため、アプリケーションでトランザクションを張った場合などは異なる動作をすることがあります。各自の実行環境で動作確認してから導入してください。
また、外部キー制約時のエラーの動作を中心に記載しました。この動作はエラーの重要度レベルによって変わることがあります。エラーレベルが異なる場合も各自で動作を確認してみてください。エラーレベルに関しては以下を参照してください。
まとめ
本記事ではXACT_ABORT
の動作について紹介しました。紹介した動作について以下の表にまとめます。
設定 | エラー時の動作 | ロールバック処理の記述 | ストアド内でのハンドリング方法 |
---|---|---|---|
OFF | 発生した処理のみがロールバック | 全ての処理をロールバックしたい場合は処理を書く必要がある | @@ERROR でのエラーハンドリングが可能 |
ON | トランザクション全体が終了しロールバック | 自動でロールバックされるので書く必要がない | TRY-CATCH でのエラーハンドリングが可能 |
最後に
カート決済部では負荷軽減の対策から、新機能開発、カート決済リプレイスなどを行っておりタスクが山積みの状態です。このような課題を一緒に進めていただける仲間を募集しています。ご興味のある方は以下のリンクから是非ご応募ください。
また、カジュアル面談も随時実施中です。是非ご応募ください。