失敗しても安全に戻すには?SQL ServerのTRY...CATCHとエラー処理の基本をやさしく解説
実運用のSQLでは、常に理想通りに処理が進むとは限りません。
一意制約違反、外部キー違反、変換エラー、タイムアウトなど、エラーが起きたときに安全に処理を巻き戻し、原因を記録し、正しく再通知できる設計が大切です。
この記事では、SQL Server のTRY...CATCH 構文を中心に、エラー情報の取得関数、トランザクションとの組み合わせ、実務でのベストプラクティスを初心者向けに解説します。
目次
TRY...CATCH構文の基本
基本形
BEGIN TRY
-- エラーが発生する可能性のある処理
END TRY
BEGIN CATCH
-- エラー時の代替処理(ログ、後片付け、再スロー 等)
END CATCH;
TRY ブロック内で実行時エラーが発生すると制御は CATCH ブロックへ移り、CATCH 内ではエラーの内容を取得・処理できます。構文エラー(パースエラー)はTRYに入る前に失敗するため捕捉できません。
THROW と RAISERROR
THROW:SQL Server 2012以降で推奨。元エラーをそのまま再送出でき、書式も簡潔。RAISERROR:レガシー手法。新規実装では THROW の利用が基本。
エラー情報を取得する関数
CATCH ブロック内で使える代表的な関数は以下です。
| 関数名 | 取得内容 | 例 |
|---|---|---|
| ERROR_NUMBER() | エラー番号 | 2627(一意制約違反 など) |
| ERROR_MESSAGE() | エラーメッセージ | 詳細なテキスト(NVARCHAR(2048)) |
| ERROR_SEVERITY() | 重大度 | 11〜16など(ユーザー修正可能は 11〜16) |
| ERROR_STATE() | 状態コード | サポートへの手掛かり |
| ERROR_LINE() | エラー発生行 | デバッグ用 |
| ERROR_PROCEDURE() | 手続き名 | SP/トリガー名など |
トランザクションとの組み合わせ(ROLLBACK/COMMIT)
定石パターン
BEGIN TRY
BEGIN TRANSACTION;
-- 更新系処理(INSERT/UPDATE/DELETE など)
COMMIT TRANSACTION;
END TRY
BEGIN CATCH
IF XACT_STATE() <> 0
ROLLBACK TRANSACTION; -- 可能なら必ず巻き戻す
-- ログ出力など(後述のサンプル参照)
THROW; -- 呼び出し元へ再通知(推奨)
END CATCH;
XACT_STATE() は現在のトランザクション状態を返します(1=コミット可能/-1=コミット不可/0=なし)。エラー時はまず安全に ROLLBACK できるか判断しましょう。
SET XACT_ABORT ON の活用
外部キー違反など一部のエラーで自動ロールバックを強制したい場合、バッチ先頭で SET XACT_ABORT ON; を指定すると堅牢性が高まります(ただし適用範囲に留意)。
実務で使えるエラーハンドリング例
① 基本:ログテーブルに記録して再スロー
-- 例)簡易ログテーブル
-- CREATE TABLE dbo.ErrorLog (
-- Id INT IDENTITY PRIMARY KEY,
-- ErrorNumber INT, Severity INT, State INT,
-- ProcName SYSNAME NULL, Line INT,
-- Message NVARCHAR(2048),
-- CreatedAt DATETIME2 DEFAULT SYSUTCDATETIME()
-- );
BEGIN TRY
BEGIN TRAN;
-- 例:在庫引当
UPDATE Inventory SET Stock = Stock - 1 WHERE ProductID = @pid;
COMMIT;
END TRY
BEGIN CATCH
IF XACT_STATE() <> 0 ROLLBACK;
INSERT INTO dbo.ErrorLog
(ErrorNumber, Severity, State, ProcName, Line, Message)
VALUES
(ERROR_NUMBER(), ERROR_SEVERITY(), ERROR_STATE(),
ERROR_PROCEDURE(), ERROR_LINE(), ERROR_MESSAGE());
THROW; -- 上位へ通知
END CATCH;
② ストアドでの標準テンプレート
CREATE OR ALTER PROCEDURE dbo.usp_UpdateOrder
@OrderID INT, @Amount DECIMAL(10,2)
AS
SET NOCOUNT ON;
SET XACT_ABORT ON;
BEGIN TRY
BEGIN TRAN;
UPDATE Orders
SET Amount = @Amount
WHERE OrderID = @OrderID;
COMMIT;
END TRY
BEGIN CATCH
IF XACT_STATE() <> 0 ROLLBACK;
DECLARE @msg NVARCHAR(2048) = ERROR_MESSAGE();
DECLARE @num INT = ERROR_NUMBER();
-- 必要に応じてログ
INSERT INTO dbo.ErrorLog(ErrorNumber, Severity, State, ProcName, Line, Message)
VALUES (@num, ERROR_SEVERITY(), ERROR_STATE(), ERROR_PROCEDURE(), ERROR_LINE(), @msg);
-- ビジネス用途ならメッセージを整形して再通知も可
THROW; -- そのまま原文で再送出(推奨)
END CATCH;
③ 業務的な再送出(サニタイズ)
内部事情を隠したい場合は、アプリ層向けの定型メッセージで再送出します。
BEGIN CATCH
IF XACT_STATE() <> 0 ROLLBACK;
-- ログは詳細に
INSERT INTO dbo.ErrorLog(...);
-- 利用者には簡潔に
THROW 50001, N'更新に失敗しました。しばらくしてから再実行してください。', 1;
END CATCH;
よくある注意点とベストプラクティス
- TRY 内での構文エラーは捕捉できない:デプロイ前に必ずコンパイル検証。ユニットテストを整備。
- 必ず ROLLBACK 可能か確認:
XACT_STATE()で状態を判定してからロールバック。 - THROW を基本に:新規実装は
THROWを使い、元のスタック情報を保持。 - SET XACT_ABORT ON の活用:更新系バッチ・ストアドでの安全性向上に有効。
- 再実行可能な設計:同一リクエストの二重実行を想定(冪等キー・楽観ロック等)。
- 監査ログは十分な項目で:ユーザーID、関連ID、入力値、アプリ識別子、UTC日時。
まとめと次のステップ
学んだ内容の整理
- TRY...CATCH で実行時エラーを捕捉し、ログと後処理を行える。
- ERROR_* 関数で詳細なエラー情報を取得し、原因調査に活用。
- トランザクションと組み合わせ、
XACT_STATE()で確実に ROLLBACK/COMMIT を制御。 - 実務では
THROWを基本に、SET XACT_ABORT ONとログ設計で堅牢化。
SQL Server学習シリーズ
参考リンク

0 件のコメント:
コメントを投稿