【実務・中級編】 PL/pgSQLの例外処理 – PostgreSQL

やあ、今日もデータベースと格闘してる?

PostgreSQLでストアドプロシージャや関数を書き始めると、必ずと言っていいほどぶち当たる壁があるよね。「処理の途中でエラーが起きたとき、どうやってスマートに後始末するか」っていう問題だ。

今日は、PL/pgSQLにおける「例外処理」の作法について、現場の知見を交えて話そうと思う。マニュアルを読めば載っているような基本の話から、ちょっとした「実務のコツ」まで、肩の力を抜いて読んでみてほしい。

—

なぜ、例外処理(Exception Handling)が必要なのか?

初心者の頃は、エラーが起きても「まあ、投げっぱなしでいいか」と思いがちだ。でも、データの一貫性が命の現場でそれをやると、目も当てられない惨事になる。

例えば、銀行の送金処理中にエラーが起きて、引き落としだけ完了して入金がされなかったら?……想像しただけで冷や汗が出るよね。PL/pgSQLの例外処理は、「不測の事態でも、最低限の安全装置を働かせるための生命線」なんだ。

—

1. エラーを自分で投げる:RAISE文

まずは基本の「RAISE」から。これはエラーを意図的に発生させるための命令だ。

RAISE EXCEPTION ‘ユーザーID % は存在しません’, user_id;

ここでポイントなのが、`%` を使ったプレースホルダー。これを使わずに文字列結合で頑張るコードを見かけるけど、読みづらい上にバグの温床になる。素直に `%` を使おう。

ちなみに、開発中なら `RAISE NOTICE` を使うのもおすすめだ。これは処理を止めずにログだけ吐いてくれる。デバッグの時に `SELECT` 文をあちこちに仕込むより、ずっとスマートだよ。

—

2. エラーを捕まえて無害化する:EXCEPTIONブロック

ここからが本題だ。処理の一部でエラーが起きても、トランザクション全体を失敗させたくない時、`BEGIN … EXCEPTION … END` ブロックが役に立つ。

DO $$
BEGIN
— 何らかの危険な処理
INSERT INTO users (id, name) VALUES (1, ‘Alice’);

EXCEPTION
WHEN unique_violation THEN
— ユニーク制約違反ならログを吐いて無視する、といった制御が可能
RAISE NOTICE ‘このIDは既に使われています。スキップします。’;
END $$;

現場で気をつけるべき「落とし穴」

この `EXCEPTION` ブロックには、一つだけ重大な注意点がある。

「EXCEPTIONブロックに入ると、そのブロック内の処理は自動的にロールバックされる」

つまり、ブロックの中でどれだけ `INSERT` や `UPDATE` を積み重ねていても、最後でエラーをキャッチした瞬間、そのブロック内でやったことはすべて「なかったこと」になるんだ。

「エラーを捕まえてから、ログテーブルにエラーログを書き込もう!」と思って同じトランザクション内で INSERT しても、そのログさえも消えてしまう。これにハマって夜を明かすエンジニアを何人も見てきたよ。ログを残したいなら、`dblink` を使って別セッションで書き込むか、そもそもエラーを出さない設計にするのが鉄則だね。

—

3. 実践的な使い方:エラーコードを分類する

`WHEN` 句を使ってエラーの種類を絞り込むのは、現場の必須スキルだ。全部 `WHEN OTHERS` で片付けるのはやめよう。あれは「何が起きたか分からない」と言っているのと同じだからね。

よく使うのはこのあたりかな。

  • `unique_violation`: 重複キーエラー(INSERTのバッティングなど)
  • `foreign_key_violation`: 外部キー制約エラー
  • `check_violation`: CHECK制約エラー

例えば、外部キー制約に引っかかった時だけ特定の処理をしたいなら、こんな風に書く。

BEGIN
DELETE FROM departments WHERE id = target_id;
EXCEPTION
WHEN foreign_key_violation THEN
RAISE EXCEPTION ‘この部署にはまだ社員が所属しています。先に社員を移動させてください。’;
END;

—

まとめ:結局のところ「予測」がすべて

例外処理は強力な武器だけど、乱用するとコードが複雑になり、処理速度も落ちる。

僕がいつも心がけているのは、「例外処理に頼りすぎない設計」だ。そもそもエラーが起きないようにチェックを入れるのが一番の正義。その上で、どうにも防げない不可抗力や、境界条件のギリギリを攻める時にだけ `EXCEPTION` を使う。

これが、メンテナンス性の高い、後輩に感謝されるコードを書くコツだよ。

どうかな、少しはイメージが湧いたかな?もし現場で「こんな時はどうすればいい?」という複雑なケースに遭遇したら、またいつでも聞いてくれ。データベースの世界は深いけれど、論理さえ押さえれば必ず攻略できるからさ。

それじゃ、また現場で会おう!

コメント

タイトルとURLをコピーしました