MySQL | SQLite経験者向け、30日で習得するMySQL:実務SQL力 - Day13 トリガー

SQL MySQL
スポンサーリンク

Day13 後半のゴール

「“INSERT をきっかけに履歴・ログを残す”設計を自分で組めるようにする」

前半で、BEFORE INSERT トリガーと NEW を使って「入ってくる値を整える」話をしました。
後半では、もう一歩踏み込んで、

INSERT をきっかけに監査ログ・履歴テーブルを自動更新する
AFTER INSERT トリガーの典型パターンを押さえる
トリガーの“やりすぎ”で起きる事故・落とし穴を知る

ここまでを目標にします。


監査ログ用テーブルを用意する

「“何がいつ作られたか”を別テーブルに残す」

まずは、よくある監査ログの形からいきます。

例えば、posts テーブルに対して、

誰が
いつ
どんなタイトルの投稿を作ったか

を別テーブルに記録したいとします。

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;

CREATE TABLE post_audit_logs (
  id         INT AUTO_INCREMENT PRIMARY KEY,
  post_id    INT          NOT NULL,
  title      VARCHAR(255) NOT NULL,
  created_at DATETIME     NOT NULL,
  note       VARCHAR(255) NOT NULL
) ENGINE=InnoDB;
SQL

post_audit_logs は、「投稿が作られた履歴」を残すためのテーブルです。
ここに、INSERT のたびに1行ずつ自動で追記していきます。


AFTER INSERT トリガーで監査ログを残す

「INSERT が確定したあとに“別テーブルへ書く”のが王道」

posts への INSERT をフックして、
post_audit_logs に1行追加するトリガーはこう書けます。

DELIMITER //

CREATE TRIGGER log_post_insert
AFTER INSERT ON posts
FOR EACH ROW
BEGIN
  INSERT INTO post_audit_logs (
    post_id,
    title,
    created_at,
    note
  ) VALUES (
    NEW.id,
    NEW.title,
    NOW(),
    'POST_CREATED'
  );
END //

DELIMITER ;
SQL

ここでのポイントを整理します。

AFTER INSERT
投稿がテーブルに書き込まれた「後」に動くので、NEW.id には確定した主キーが入っている。

NEW.id / NEW.title
挿入された投稿のIDとタイトルを、そのまま監査ログにコピーしている。

NOW()
監査ログ側の created_at は、「ログが記録された時刻」として NOW() を使っている。

note
「どういうイベントか」を文字列で残しておくと、後で検索しやすい。

このトリガーがあると、アプリ側は何も意識せずに

INSERT INTO posts (title, body, created_at)
VALUES ('タイトル', '本文', NOW());
SQL

とするだけで、
裏で必ず post_audit_logs に1行追加されます。


「履歴テーブル」という考え方

「元テーブルは“最新状態”、履歴テーブルは“過去のスナップショット”」

監査ログや履歴テーブルを設計するとき、
よく出てくる考え方がこれです。

元テーブル(posts)
今の状態だけを持つ

履歴テーブル(post_audit_logs)
「いつ、どんな状態だったか」のスナップショットを積み上げる

INSERT 時だけでなく、UPDATE / DELETE 時にもトリガーを仕込めば、

作られたときの状態
変更されたときの状態
削除されたときの状態

を全部履歴として残すこともできます。

Day13 は INSERT に絞っていますが、
「履歴テーブル」という発想は、
UPDATE / DELETE トリガーにもそのままつながります。


セキュリティ・コンプライアンスの視点

「“消せないログ”をDB側で確保する」

監査ログをトリガーで残す一番の意味は、
「アプリがサボっても、ログだけは必ず残る」ことです。

アプリ側で

「投稿が作られたら、ログテーブルにもINSERTしてね」

と書いておくのは簡単ですが、

一部のコードパスで書き忘れる
バグでログINSERTだけ失敗する
悪意ある開発者がログINSERTをコメントアウトする

といったリスクがあります。

トリガーでDB側に埋め込んでおけば、

posts に INSERT した瞬間に、
必ず post_audit_logs にも1行入る

という状態を作れます。

さらに、権限設計を工夫すれば、

アプリユーザーには post_audit_logs の DELETE 権限を与えない
管理者だけが参照できる

といった構成も取れます。

これは、
不正アクセス調査やコンプライアンス対応で
「ログが消されていないか?」を確認するうえで、とても重要です。


トリガーの落とし穴①:見えない副作用

「アプリからは“INSERTしただけ”に見える」

トリガーの怖さの1つは、
アプリ側から見ると「何が起きているか見えない」ことです。

開発者がコードを読んでいても、

INSERT INTO posts ...

としか書いていないのに、
実際には裏で

監査ログにINSERT
別テーブルの集計をUPDATE
さらに別のトリガーが連鎖して…

といったことが起きている可能性があります。

これが行き過ぎると、

「このINSERT、なんでこんなに遅いの?」
「なんでこのテーブルの行数が勝手に増えてるの?」

といった“謎挙動”に見えてしまいます。

Day13 の段階で意識しておきたいのは、

トリガーは強力なぶん、「どこに何があるか」をちゃんと共有・ドキュメント化しないと、チーム全体の理解からズレていく

ということです。


トリガーの落とし穴②:パフォーマンスと連鎖

「INSERT 1回で“どれだけの処理”が走るかを意識する」

もう1つの落とし穴は、パフォーマンスです。

1回の INSERT で、

トリガーAが動く
→ その中で別テーブルにINSERT
→ そのテーブルにもトリガーBが付いている
→ さらに別の処理が…

という連鎖が起きると、
アプリ側からは「INSERT 1回」のつもりでも、
DBの中ではかなりの処理が走っていることがあります。

特に、

大量INSERT(バルクインサート)
バッチ処理で何万行もINSERT

のような場面では、

トリガーの中身が重いと、一気にDBが悲鳴を上げます。

実務では、

トリガーの中で外部APIを叩かない
トリガーの中で時間のかかる集計をしない
必要以上に多くのテーブルを触らない

といった「トリガーの中でやることの上限」を決めておくのが安全です。


どこまでトリガーに任せるか

「“最低限守りたいルール”だけをDB側に置く」

ここまでの話を、設計の視点で一言にまとめるとこうなります。

トリガーに任せるべきなのは、

必ず守りたい最低限のルール
(監査ログ、created_at の自動セットなど)

アプリごとに実装がバラけると困る処理
(共通の履歴テーブル更新など)

逆に、トリガーに入れすぎるとつらくなるのは、

画面ごとの細かいビジネスロジック
重い集計や外部連携

です。

Day13 の時点では、

トリガー=「DBが自分を守るための、自動ガードレール」

くらいの位置づけで捉えておくと、
バランスが取りやすくなります。


Day13 後半のまとめ

AFTER INSERT トリガーは、「INSERT が確定したあとに別テーブルへログや履歴を自動で書き込む」用途に向いており、CREATE TRIGGER log_post_insert AFTER INSERT ON posts FOR EACH ROW ... のように定義して NEW.idNEW.title を使えば、投稿が作られるたびに post_audit_logs のような監査テーブルへ「いつ・どの投稿が作られたか」を確実に記録できる。
こうした履歴テーブルは、元テーブルが「現在の状態」だけを持つのに対し、「いつ・どんな状態だったか」というスナップショットを積み上げる役割を持ち、トリガーで自動更新することで、アプリがログINSERTをサボっても「最低限の監査情報だけは必ず残る」状態をDB側で保証できる一方、トリガーはアプリから見えない副作用になりやすく、連鎖や重い処理を入れすぎると1回のINSERTで大量の処理が走ってパフォーマンス問題や“謎挙動”の原因になるため、「監査・最低限ルールだけをトリガーに任せ、重いビジネスロジックや外部連携はアプリやバッチ側に出す」という線引きが実務では重要になる。

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