はじめに
Databaseを使っているとこんな要件はありませんか?
- ステータス更新の履歴を残したい
- 誰がいつ更新したかを記録したい
- 更新前と更新後の値を保存したい
実務ではこういった監査ログを実装することがあり、
その時に便利なのがトリガー(trigger)です。
この記事では、
- トリガーの基礎
- トリガーの基本構文
- 実務で使える例
- 注意点
について解説していきます。
トリガーの基礎
トリガーとは、「テーブルで INSERT(挿入) / UPDATE(更新) / DELETE(削除) がトリガーとなり、自動で実行される処理」 のことです。
実行される処理はあらかじめ定義された処理が実行されます。
処理の定義については次の「トリガーの基本構文」で解説します。
トリガーの基本構文
CREATE TRIGGER トリガー名
{ BEFORE | AFTER }
{ INSERT | UPDATE | DELETE }
ON テーブル名
FOR EACH ROW
BEGIN
-- 実行したい処理
END;
①対象のテーブル
トリガーは「どのテーブルにぶら下げるか」が最初の設計です。
CREATE TRIGGER after_update_orders
AFTER UPDATE ON orders
orders が対象テーブルです。
②トリガーを起動するイベント
トリガーは以下のイベントで起動します。
- INSERT(挿入)
- UPDATE(更新)
- DELETE(削除)
CREATE TRIGGER after_update_orders
AFTER UPDATE ON orders
UPDATE がトリガーを起動するイベントになります。
INSERTの場合
- NEWのみ存在
- 初期値保存や履歴生成に使われる
UPDATEの場合
- NEWとOLDが存在
- 「何かが変更されたら」ではなく「特定カラムが変更されたら」
DELETEの場合
- OLDのみ存在
- 削除後のデータは消える
ポイント
- NEW → 更新後の値
- OLD → 更新前の値(UPDATEやDELETEで使える)
③トリガーの起動タイミング
CREATE TRIGGER after_update_orders
AFTER UPDATE ON orders
AFTER がトリガーを起動するタイミングになります。
BEFORE
特徴:
- NEWを書き換えられる
- バリデーション可能
用途:
- 自動補正
- 更新値の強制
例:強制的にupdated_atを更新する
SET NEW.updated_at = NOW();
アプリ側での更新漏れや誤操作を防ぐことができます。
AFTER
特徴:
- 実際に確定した値を扱える
用途:
- ログ用途に最適
イベントとタイミング
| イベントとタイミング | |
|---|---|
| INSERT BEFORE | データ挿入前 |
| INSERT AFTER | データ挿入後 |
| UPDATE BEFORE | データ更新前 |
| UPDATE AFTER | データ更新後 |
| DELETE BEFORE | データ削除前 |
| DELETE AFTER | データ削除後 |
④トリガーの処理内容
トリガーは元のSQLと同一トランザクション内で実行されます。
つまり、
- 失敗すれば本処理も失敗
- ロールバックも連動
処理内容の設計原則
原則1:副作用は小さく
良い例:
- ログ保存
- フラグ補正
- 軽い整合性チェック
悪い例:
- 集計更新
- 別テーブルへの大量更新
- 複雑なビジネスロジック
原則2:条件を明確にし、コメントを書く
トリガーはブラックボックス化しやすいため、ドキュメント化は必須です。
ドキュメントで条件を明確にし、SQLでもコメントを残すと処理の可読性が上がります。
実務で使える例
例えば、商品の管理システムを想定し、受注データを管理するテーブルとステータスの変更ログテーブルを作成します。
①対象テーブル
orders
CREATE TABLE orders (
id INT AUTO_INCREMENT PRIMARY KEY,
customer_name VARCHAR(100),
status VARCHAR(50),
created_at DATETIME,
updated_at DATETIME,
updated_by INT
);
想定する注文の「ステータス」
- 受付中
- 支払済
- 発送済
- 配達完了
- キャンセル
②ログテーブル
orders_log
CREATE TABLE status_change_log (
id INT AUTO_INCREMENT PRIMARY KEY,
order_id INT,
old_status VARCHAR(50),
new_status VARCHAR(50),
created_at DATETIME,
updated_by INT
);
③トリガー名
after_update_orders
④イベントとタイミング
AFTER UPDATE(更新後)
⑤トリガーの作成
CREATE TRIGGER after_update_orders
AFTER UPDATE ON orders
FOR EACH ROW
BEGIN
-- ステータスが変更された場合のみログを残す
IF OLD.status <> NEW.status THEN
INSERT INTO orders_log (
order_id,
old_status,
new_status,
created_at,
updated_by
)
VALUES (
order_id
OLD.status,
NEW.status,
OLD.user_id,
NEW.user_id,
NOW(),
user_id
);
END IF;
END;
注文からキャンセルまでの処理の流れ
- アプリから注文が入る
- ステータスを支払済にUPDATE
- 更新成功
- AFTER UPDATEトリガー発火
- 更新前と更新後のstatusが異なる場合orders_logにログをINSERT
- その後管理者が注文をキャンセル
- 3,4,5の処理
ログテーブルのデータ
| id | order_id | old_status | new_status | created_at | updated_by |
|---|---|---|---|---|---|
| 1 | 1 | 受付中 | 支払済 | 2026/02/24 10:00:00 | (顧客のID) |
| 2 | 1 | 支払済 | キャンセル | 2026/02/24 10:05:00 | (管理者のID) |
ordersテーブルだけではデータが上書きされてしまうので更新履歴を遡ることはできませんが、このトリガーの処理によって「いつ」、「誰が」、「更新前と更新後の値」をログテーブルに残すことができます。
トリガーの注意点
①デバッグがしにくい
トリガーは裏で動くため、「なぜこのデータが入ったのか?」が分かりにくくなります。
②パフォーマンスに影響
大量INSERT時に重い処理を書くとパフォーマンスが落ちます。
③依存関係が見えづらい
スキーマだけ見ても処理内容が分かりづらいです。
確認コマンド:
SHOW TRIGGERS;
まとめ
トリガーは「データベースレベルで自動処理を実行できる強力な機能」ですが、
便利な反面、設計を誤るとブラックボックス化します。
使うなら
- 目的を明確にする
- シンプルに保つ
- 必ずドキュメント化する
この3点を意識しましょう。