Day13 前半のゴール
「“INSERT にフックする自動処理”としてトリガーをイメージできるようにする」
今日のテーマはトリガー(TRIGGER)です。
ここから一気に「DBが自分で動き出す」感じが強くなります。
まず一言で言うと、トリガーはこうです。
テーブルに対する INSERT / UPDATE / DELETE が起きたときに
自動的に実行される“隠れストアドプロシージャ”
Day13 前半のゴールは、
INSERT トリガーの基本イメージを持つ
NEW という“挿入される行”の疑似レコードを理解する
「INSERTした瞬間に何かを自動でやらせる」典型パターンを知る
ここまで行けば、後半の「監査ログ」「セキュリティ寄りの使い方」がスッと入ります。
トリガーとは何か
「“この操作が起きたら、この処理を自動で実行してね”という約束」
ストアドプロシージャは、CALL 名前(...) と明示的に呼び出しました。
トリガーは違います。
アプリや人間が普通に INSERT したときに、
裏で勝手に動く処理です。
イメージとしては、
「このテーブルに INSERT があったら、そのたびにこの処理を実行してね」
とDBにお願いしておく感じです。
ここで大事なのは、
アプリ側はトリガーの存在を意識しなくても動く
でも、トリガーは確実に実行される
という点です。
だからこそ「DB側のルール」として強い力を持ちます。
INSERT トリガーの基本構文
「BEFORE / AFTER と NEW をまず押さえる」
MySQL の INSERT トリガーは、ざっくりこういう形です。
CREATE TRIGGER トリガー名
BEFORE INSERT ON テーブル名
FOR EACH ROW
BEGIN
-- ここに自動処理を書く
END;
SQLまたは
CREATE TRIGGER トリガー名
AFTER INSERT ON テーブル名
FOR EACH ROW
BEGIN
-- ここに自動処理を書く
END;
SQLここで出てくるキーワードが重要です。
BEFORE / AFTER
INSERT が実行される「前」に動くか、「後」に動くかを指定します。
FOR EACH ROW
1回のINSERTで何行入っても、「1行ごとに」トリガーが実行されることを意味します。
そして、トリガーの中では、
NEW.カラム名
という特別な名前で、「挿入される行の値」にアクセスできます。
NEW は「これからテーブルに入る1行」を表す疑似レコードです。
INSERT トリガーでは、この NEW が主役になります。
具体例:created_at を自動セットする
「アプリ側で書き忘れても、DBが必ず入れてくれる」
まずは、よくあるシンプルな例からいきます。
次のようなテーブルを考えます。
CREATE TABLE posts (
id INT AUTO_INCREMENT PRIMARY KEY,
title VARCHAR(255) NOT NULL,
body TEXT NOT NULL,
created_at DATETIME NOT NULL
) ENGINE=InnoDB;
SQL本来なら、INSERT するときにアプリ側で created_at をセットします。
INSERT INTO posts (title, body, created_at)
VALUES ('タイトル', '本文', NOW());
SQLでも、現実にはこういうことが起きます。
開発者が created_at を入れ忘れる
別のアプリが雑に INSERT してくる
テスト用スクリプトが適当なINSERTを投げる
こういうときに「必ず現在時刻を入れたい」のなら、
トリガーでDB側にルールを持たせるのが強いです。
BEFORE INSERT トリガーで値を書き換える
「NEW.カラム名 に代入できる」
created_at を自動セットするトリガーは、こう書けます。
DELIMITER //
CREATE TRIGGER set_posts_created_at
BEFORE INSERT ON posts
FOR EACH ROW
BEGIN
IF NEW.created_at IS NULL THEN
SET NEW.created_at = NOW();
END IF;
END //
DELIMITER ;
SQLここで起きていることを分解します。
BEFORE INSERT
INSERT が実際にテーブルに書き込まれる「前」に動きます。
NEW.created_at
これから挿入される行の created_at の値です。
IF NEW.created_at IS NULL THEN
アプリ側が値を入れてこなかった(NULLのまま)場合だけ、
SET NEW.created_at = NOW();
現在時刻を自動でセットします。
重要なのは、
BEFORE INSERT トリガーでは、NEW.カラム名 に代入できる
という点です。
つまり、「入ってくる値を上書きできる」のです。
このトリガーがあると、
アプリが created_at を指定してINSERTした場合
→ その値がそのまま使われる
アプリが created_at を指定しなかった場合
→ トリガーが NOW() を入れてくれる
という動きになります。
AFTER INSERT トリガーとの違い
「“値をいじる”なら BEFORE、“別テーブルに書く”なら AFTER が自然」
INSERT トリガーには、BEFORE と AFTER の2種類があります。
BEFORE INSERT
テーブルに書き込まれる前に動く
→ NEW の値を上書きできる
AFTER INSERT
テーブルに書き込まれた後に動く
→ NEW の値は読み取り専用(基本的に変更しない前提)
「入ってくる値を整える」「足りない値を補う」といった用途は、
BEFORE INSERT が向いています。
一方で、
INSERT された内容をもとに、
別テーブルにログを残す
集計テーブルを更新する
といった「副作用的な処理」は、
AFTER INSERT の方が自然です。
Day13 前半では、
値をいじる系 → BEFORE
後処理・ログ系 → AFTER
というざっくりしたイメージだけ持っておけばOKです。
セキュリティの観点から見た「必ず入る値」
「アプリがサボっても、DBが守ってくれるラインを決める」
トリガーは、セキュリティや監査の観点でも強い武器になります。
例えば、
created_at(作成日時)
created_by(作成したユーザーID)
client_ip(クライアントIP)
こういった「誰が・いつ・どこから」的な情報は、
後から不正調査やトラブル解析で効いてきます。
でも、アプリ側に「必ず入れてね」とお願いするだけだと、
どこかで抜け漏れが出ます。
トリガーで、
「このテーブルにINSERTがあったら、必ず created_at を入れる」
「セッションユーザーIDを created_by に入れる(※工夫が必要)」
といったルールをDB側に持たせておくと、
アプリがサボっても、最低限の情報は必ず残る
という状態を作れます。
Day13 前半では、
トリガー=「アプリの外側で、DBが自分を守る仕組み」
という視点も、少し意識しておいてほしいです。
SQLite と MySQL の“自動処理”の違い
「SQLite時代は“アプリで頑張る”、MySQLでは“DBに任せる”選択肢が増える」
SQLite では、トリガー機能はあるものの、
小規模アプリではあまり使われないことが多いです。
理由はシンプルで、
アプリ1つだけがDBを触る
ロジックも1か所にまとまっている
という世界観だからです。
MySQL では、
複数のアプリが同じDBを触る
バッチや管理ツールも同じテーブルにINSERTする
といった状況が増えます。
このとき、
「このテーブルにINSERTするなら、最低限これだけは必ずやる」
というルールを、
アプリごとにバラバラに実装するのは危険です。
トリガーは、
その“最低限ライン”をDB側に一本化する
ための仕組みだと考えると、
位置づけが分かりやすくなります。
Day13 前半のまとめ
トリガーは「テーブルに対する INSERT / UPDATE / DELETE が起きたときに、自動的に実行される隠れストアドプロシージャ」のようなもので、INSERT トリガーでは CREATE TRIGGER 名前 BEFORE/AFTER INSERT ON テーブル FOR EACH ROW BEGIN ... END の形で定義し、トリガー内からは NEW.カラム名 を通じて「挿入される行の値」にアクセスできる。
BEFORE INSERT トリガーでは SET NEW.created_at = NOW(); のように NEW の値を書き換えることができるため、「アプリが created_at を入れ忘れたら自動で現在時刻をセットする」といった“値の補完・整形”に向いており、一方で AFTER INSERT トリガーは「INSERT が確定した後に、別テーブルへログを残す・集計テーブルを更新する」といった“後処理”に向いている。
SQLite 時代は「INSERT時の自動処理」をアプリ側に任せがちだったが、MySQL ではトリガーを使うことで、「このテーブルにINSERTするなら、必ず created_at などの監査情報を残す」といった“最低限守りたいルール”をDB側に一本化でき、アプリがサボってもDB自身が自分を守るラインを作れるようになる。
後半では、
AFTER INSERT トリガーでの監査ログ・履歴テーブルの作り方、
トリガーの書きすぎで起きる事故やパフォーマンスの落とし穴、
そして「どこまでトリガーに任せるか」という設計の線引きを、
具体的な例と一緒に掘り下げていきます。
