7.

SQLトリガー入門|INSERT/UPDATE/DELETE自動実行・BEFORE/AFTER

編集
この記事の要点
  • トリガー (Trigger) はテーブルに対するINSERT/UPDATE/DELETE を契機に自動実行される DB オブジェクト
  • 実行タイミングは BEFORE / AFTER / INSTEAD OF、対象はFOR EACH ROW (行単位) / FOR EACH STATEMENT (文単位)
  • 主な用途: 監査ログ記録 / 自動採番 / 整合性チェック / 集計テーブル同期
  • 副作用が見えにくいため多用は禁物。バルク処理時の性能劣化や無限ループに注意
  • 近年はアプリ層 / Change Data Capture に処理を寄せる設計も増えている

トリガーとは

トリガー (Trigger) はデータベース内に保存されるプロシージャの一種で、特定のテーブルに対して INSERT / UPDATE / DELETE が実行されたときに自動的に呼び出される仕組みです。アプリケーション側で明示的に呼ばなくても DBMS が裏で発火させるため、データ更新を「フック」するために使われます。

主な用途

用途
監査ログUPDATE のたびに変更前後の値を audit テーブルに残す
整合性チェック業務ルールに反する値を弾く / 補正する
自動採番・タイムスタンプINSERT 時に created_at を自動セット
集計テーブル同期明細テーブルの変更を集計表に反映
非正規化テーブルの維持JOIN 高速化用のサマリ列を更新

発火タイミング・対象

分類意味
BEFORE変更が確定するに実行。値の補正や事前検証に
AFTER変更が確定したに実行。監査ログや別表更新に
INSTEAD OF主にビューに対し、本来の更新の代わりに実行
FOR EACH ROW影響を受けた行ごとに発火
FOR EACH STATEMENTSQL 文 1 回ごとに発火 (行数に依存しない)

Oracle のトリガー

-- 監査ログを残すトリガー
CREATE OR REPLACE TRIGGER trg_emp_audit
AFTER UPDATE ON employees
FOR EACH ROW
BEGIN
    INSERT INTO emp_audit (emp_id, old_salary, new_salary, changed_at)
    VALUES (:OLD.emp_id, :OLD.salary, :NEW.salary, SYSTIMESTAMP);
END;
/

-- BEFORE で自動採番 (シーケンス + トリガー)
CREATE OR REPLACE TRIGGER trg_emp_id
BEFORE INSERT ON employees
FOR EACH ROW
BEGIN
    IF :NEW.emp_id IS NULL THEN
        SELECT emp_seq.NEXTVAL INTO :NEW.emp_id FROM dual;
    END IF;
END;
/

PostgreSQL のトリガー

PostgreSQL ではトリガーから関数 (PL/pgSQL) を呼ぶ二段構えです。

-- 1. トリガー関数を定義
CREATE OR REPLACE FUNCTION fn_emp_audit() RETURNS trigger AS $$
BEGIN
    INSERT INTO emp_audit (emp_id, old_salary, new_salary, changed_at)
    VALUES (OLD.emp_id, OLD.salary, NEW.salary, now());
    RETURN NEW;
END;
$$ LANGUAGE plpgsql;

-- 2. トリガーをテーブルに紐付け
CREATE TRIGGER trg_emp_audit
AFTER UPDATE ON employees
FOR EACH ROW
EXECUTE FUNCTION fn_emp_audit();

MySQL のトリガー

DELIMITER //

CREATE TRIGGER trg_emp_audit
AFTER UPDATE ON employees
FOR EACH ROW
BEGIN
    INSERT INTO emp_audit (emp_id, old_salary, new_salary, changed_at)
    VALUES (OLD.emp_id, OLD.salary, NEW.salary, NOW());
END//

DELIMITER ;

OLD / NEW の使い方

操作OLDNEW
INSERT使用不可挿入される行
UPDATE更新前の行更新後の行
DELETE削除される行使用不可

削除と確認

-- Oracle
DROP TRIGGER trg_emp_audit;
SELECT trigger_name, status FROM user_triggers;

-- PostgreSQL
DROP TRIGGER trg_emp_audit ON employees;
SELECT tgname, tgenabled FROM pg_trigger WHERE NOT tgisinternal;

-- MySQL
DROP TRIGGER trg_emp_audit;
SHOW TRIGGERS;

トリガー利用の注意点

  • 挙動が暗黙的になるためアプリ開発者から「なぜか値が変わる」と見える
  • BEFORE トリガーで例外を投げると元の DML がロールバックされる
  • 同じテーブルを更新するトリガーは無限ループのリスク
  • 大量行を 1 文で更新する場合、行トリガーは性能ボトルネックになりがち
  • 監査要件ならトリガー、業務ロジックならアプリ層または CDC が望ましい場面が多い

FAQ

Q: トリガーを使うべき / 避けるべき?
A: 「他に手段がない監査・整合性」ならトリガー。業務ロジックや集計ならアプリ層 / バッチ / マテリアライズド・ビュー / CDC の方が保守しやすいことが多い。

Q: ストアドプロシージャとの違い
A: プロシージャは明示的に呼び出す、トリガーはイベントで自動起動。発火条件があるのが本質的な違いです。

Q: トリガーを一時的に止めたい
A: Oracle ALTER TRIGGER ... DISABLE / PostgreSQL ALTER TABLE ... DISABLE TRIGGER ... / MySQL は DROP 必須。

Q: トリガーの中で同じテーブルを更新したい
A: Oracle では mutating table エラーが出る古典的な問題があります。複合トリガーを使うか、設計を見直して別テーブル更新にするのが定石。PostgreSQL/MySQL では制限がゆるい一方、無限ループに注意。

Q: パフォーマンスの目安
A: 行トリガーは1 行ごとのオーバーヘッドが無視できないため、数万行の一括 UPDATE には致命的になることも。バルク処理が多いなら文トリガー + 自前ロジックで一括処理した方が圧倒的に速いです。

Q: アプリと DB のどちらにロジックを置く?
A: 近年の傾向はアプリ層に寄せるです。理由は: 1) コードが Git で管理できる、2) テストしやすい、3) 横展開しやすい (Read レプリカ・CDC との相性)。監査・整合性などインフラに近い役割だけ DB に残すのが落としどころ。

まとめ

トリガーはうまく使えば強力な整合性ツールですが、設計を誤ると挙動の見えない地雷と化します。「アプリから見えない処理が走るか」「バルク更新で性能を出せるか」を常に天秤にかけ、必要最小限の場所にだけ使うのが鉄則です。代替案として CDC (Debezium) / マテリアライズドビュー / アプリ層イベントなども検討しましょう。

関連

  • ストアドプロシージャ — 明示呼び出しの DB ロジック
  • ビュー — INSTEAD OF トリガーの主な対象
  • 制約 — CHECK / FK で実現できる整合性は制約を優先
編集
Post Share
子ページ

子ページはありません

同階層のページ
  1. 表(テーブル)
  2. 索引(インデックス)
  3. ビュー
  4. 制約
  5. 順序(シーケンス)
  6. シノニム
  7. トリガー
  8. パッケージ
  9. ストアド・ファンクション
  10. ストアド・プロシージャ
  11. ユーザー(USER)

最近更新/作成されたページ