さて、みんな、もうトリガーの基本や種類、動作原理を知ってるし、いろんな用途で作れるようになったよね。でも、プログラミングでは「できること」を知るのと同じくらい、「やっちゃダメなこと」を知るのも大事なんだ。今日はトリガーを使うときにありがちなミスを一緒に見ていくよ。これを知っておけば、デバッグに何時間も、もしかしたら何日も無駄にしなくて済むからね。
トリガーの再帰:トリガーが自分自身を呼び出す
これは初心者が一番やりがちなミスかな。例えば、テーブルのカラムlast_modifiedを更新するトリガーを作ったとする。でも、その更新操作自体がまたトリガーを呼んじゃう。これで無限ループになって、最終的にサーバーがスタックオーバーフローで落ちちゃうんだ。
例:
CREATE OR REPLACE FUNCTION update_last_modified()
RETURNS TRIGGER AS $$
BEGIN
-- last_modifiedフィールドを更新
UPDATE my_table
SET last_modified = NOW()
WHERE id = NEW.id;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER after_update
AFTER UPDATE ON my_table
FOR EACH ROW
EXECUTE FUNCTION update_last_modified();
どこがダメか分かる?関数内のUPDATEが同じトリガーをまた呼んじゃうんだ。これで無限ループ完成。
どうやって防ぐか:
OLDを使って値が本当に変わったかチェックしてから更新しよう:
CREATE OR REPLACE FUNCTION update_last_modified_safe()
RETURNS TRIGGER AS $$
BEGIN
-- 値が変わったかチェック
IF NEW.last_modified IS DISTINCT FROM OLD.last_modified THEN
NEW.last_modified = NOW();
END IF;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
トリガー内で余計な操作を呼ばないように気をつけて!
OLDとNEWの誤用
この変数たちはトリガーでめっちゃ便利だけど、使い方を間違えると頭痛のタネになる。OLDは行が変更される前のデータ、NEWは変更後に保存されるデータだよ。
よくあるミスは、変数が使えない場面で使おうとしちゃうこと。例えばBEFORE INSERTトリガーではOLDは使えない。だって、まだ行ができてないからね。
ミスの例:
-- 挿入時はOLDが存在しないのでエラーになる
CREATE OR REPLACE FUNCTION log_inserts()
RETURNS TRIGGER AS $$
BEGIN
INSERT INTO audit_log (old_data, new_data)
VALUES (OLD.my_column, NEW.my_column);
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
どうやって防ぐか:
OLDとNEWを使う場面をちゃんと選ぼう:
OLDはUPDATEとDELETEで使える。NEWはINSERTとUPDATEで使える。
1つの操作に複数トリガー
PostgreSQLでは、同じ操作・同じテーブルに複数のトリガーを作れる。一見便利そうだけど、トリガー同士が競合したり、同じデータを変更しちゃうとカオスになるよ。
例:
-- トリガー1
CREATE OR REPLACE FUNCTION trigger_one()
RETURNS TRIGGER AS $$
BEGIN
-- トリガー1のロジック
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
-- トリガー2
CREATE OR REPLACE FUNCTION trigger_two()
RETURNS TRIGGER AS $$
BEGIN
-- トリガー2のロジック
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
-- 2つのトリガーを作成
CREATE TRIGGER trigger_one AFTER INSERT ON my_table EXECUTE FUNCTION trigger_one();
CREATE TRIGGER trigger_two AFTER INSERT ON my_table EXECUTE FUNCTION trigger_two();
どっちのトリガーもmy_tableへのINSERTで動く。ロジックがうまく同期できてないと、予想外の結果になるかも。
どうやって防ぐか:
- トリガーの設計は事前にちゃんと考えよう。
- 同じロジックなら1つのトリガーにまとめよう。
パフォーマンスの問題
トリガーは関連する操作ごとに追加の処理をするから、操作やレコードが多いテーブルで使うとパフォーマンスがガクッと落ちることがある。
悪い例:
CREATE OR REPLACE FUNCTION heavy_trigger_function()
RETURNS TRIGGER AS $$
BEGIN
-- 各行の更新ごとに重い処理を実行
PERFORM some_heavy_query();
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER performance_killer AFTER UPDATE ON huge_table EXECUTE FUNCTION heavy_trigger_function();
どうやって防ぐか:
- トリガー内のロジックは最小限に。重い処理はバックグラウンドジョブに回すのもアリ。
WHEN条件でトリガーの発動を制限しよう:
CREATE TRIGGER optimized_trigger
AFTER UPDATE ON my_table
WHEN (OLD.column_name IS DISTINCT FROM NEW.column_name)
EXECUTE FUNCTION light_function();
トリガーとトランザクション
トリガーは、君のクエリで始まったトランザクションの中で実行される。もしトリガー内でエラーが起きたら、トランザクション全体がロールバックされる。これは便利な場合もあるけど、エラーハンドリングを考えてないと予想外の問題になることも。
ミスの例:
CREATE OR REPLACE FUNCTION error_prone_trigger()
RETURNS TRIGGER AS $$
BEGIN
-- わざとエラーを発生
RAISE EXCEPTION '何かがおかしいよ!';
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
このトリガーが動くと、メインのクエリのトランザクションもロールバックされちゃう。
どうやって防ぐか:
トリガー内でエラーハンドリングを入れて、メイントランザクションへの影響を最小限にしよう:
CREATE OR REPLACE FUNCTION safe_trigger()
RETURNS TRIGGER AS $$
BEGIN
BEGIN
-- エラーが起きるかもしれないコード
INSERT INTO another_table VALUES (NEW.data);
EXCEPTION
WHEN OTHERS THEN
RAISE NOTICE 'エラーが発生したけど、ちゃんと処理したよ。';
END;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
実践的なアドバイス
トリガーはできるだけシンプルに。 もしトリガーが大きすぎたり複雑だと感じたら、関数を分けたりロジックを見直そう。
まずは少量のデータでテストしよう。 重要なテーブルにトリガーを付ける前に、テスト環境のデータで動作確認しよう。
トリガーのドキュメントを書こう。 数ヶ月後には、なんでそのトリガーを作ったのか自分も同僚も忘れてるかも。ちゃんとドキュメントを書いておけば後で困らないよ。
アプリ側でできることはトリガーでやらない。 トリガーは即時実行が必要な自動化には便利だけど、複雑なビジネスロジックには向かないこともあるから注意。
パフォーマンスに気を配ろう。 データ量や負荷が増えたとき、トリガーがDBのパフォーマンスにどう影響してるか常にモニタリングしよう。
これらのアドバイスと、今日学んだ知識があれば、ただトリガーを作るだけじゃなくて、ちゃんと動いて効率的でトラブルのないトリガーを書けるようになるよ!
GO TO FULL VERSION