📢 Webサイト閉鎖と移転のお知らせ
このWebサイトは2026年9月に閉鎖いたします。
新しい記事は移転先で追加しております。(旧サイトでは記事を追加しておりません)

概要

MySQLのトリガー (Trigger) は、テーブルに対する INSERTUPDATEDELETE の操作が行われた際に、自動的に実行される特別なストアドプロシージャである。
トリガーは、データの整合性チェック、自動値設定、監査ログの記録、集計テーブルの更新等、様々な用途に利用される。

トリガーには、BEFOREトリガーAFTERトリガー の2種類のタイミングがある。

  • BEFOREトリガー
    行の変更が行われる前に実行され、データの検証や値の変更に使用される。
  • AFTERトリガー
    行の変更が行われた後に実行され、監査ログの記録や集計テーブルの更新に使用される。


トリガーは、INSERTUPDATEDELETE の3種類のイベントに対応している。
各トリガーは、NEW疑似レコード (新しい値) および OLD疑似レコード (変更前の値) を使用して、変更されるデータにアクセスできる。

MySQLでは、同一テーブル・同一タイミング・同一イベントの複数トリガーが存在する場合、FOLLOWS句とPRECEDES句を使用して実行順序を制御できる。

トリガーは、FOR EACH ROW (行レベルトリガー) のみをサポートしており、ステートメントレベルトリガーはサポートされていない。
つまり、影響を受ける各行に対して1回ずつトリガーが実行される。

トリガーにはいくつかの制限事項があり、トリガー内から同じテーブルへの変更操作は不可、トリガー内でのCOMMIT / ROLLBACKは不可などの制約がある。


トリガーの作成

基本構文

トリガーを作成するには、CREATE TRIGGER文を使用する。

 CREATE
    [DEFINER = user]
    TRIGGER [IF NOT EXISTS] <トリガー名>
    <トリガータイミング> <トリガーイベント>
    ON <テーブル名> FOR EACH ROW
    [<トリガー順序>]
    <トリガー本体>
 
 trigger_time: { BEFORE | AFTER }
 
 trigger_event: { INSERT | UPDATE | DELETE }
 
 trigger_order: { FOLLOWS | PRECEDES } other_trigger_name


CREATE TRIGGER文の主要な構成要素
要素 説明
DEFINER トリガーの定義者を指定する。
省略した場合は、現在のユーザが定義者になる。
IF NOT EXISTS トリガーが既に存在する場合にエラーにならず、警告のみが出力される。
(MySQL 8.0.29以降)
trigger_name トリガー名を指定する。
データベース内で一意である必要がある。
trigger_time トリガーの実行タイミングを指定する。
BEFORE または AFTER
trigger_event トリガーを起動するイベントを指定する。
INSERTUPDATEDELETE
tbl_name トリガーを設定するテーブル名を指定する。
FOR EACH ROW 行レベルトリガーであることを示す。
MySQLでは必須
trigger_order 同一タイミング・同一イベントの複数トリガーがある場合の実行順序を指定する。
FOLLOWS または PRECEDES
trigger_body トリガーで実行する処理を記述する。
複数のSQL文を実行する場合は、BEGINとENDで囲む。


簡潔なトリガーの例を以下に示す。

 DELIMITER //
 
 CREATE TRIGGER users_before_insert
 BEFORE INSERT ON users
 FOR EACH ROW
 BEGIN
    SET NEW.created_at = NOW();
 END//
 
 DELIMITER ;


DELIMITER コマンドは、SQL文の区切り文字を一時的に変更するために使用される。
トリガー内にセミコロンを含む場合、デフォルトの区切り文字 (セミコロン) では正しく解析されないため、一時的に別の区切り文字 (通常は //) に変更する。

トリガータイミング

トリガータイミングには、BEFOREとAFTERの2種類がある。

トリガータイミングの比較
タイミング 実行タイミング 用途 NEW.col_nameへの代入
BEFORE 行の変更が行われる前に実行される データの検証、値の自動変更 可能
AFTER 行の変更が行われた後に実行される 監査ログの記録、集計テーブルの更新 不可 (読み取り専用)


BEFOREトリガーでは、SET NEW.col_name = value の構文を使用して、挿入または更新される値を変更できる。
AFTERトリガーでは、NEWおよびOLD疑似レコードは読み取り専用であり、値を変更できない。

トリガーイベント

トリガーイベントには、INSERT、UPDATE、DELETEの3種類がある。

トリガーイベントとNEW / OLDの使用可否
イベント 説明 NEW OLD
INSERT 新しい行が挿入されるときに実行される 使用可能 使用不可
UPDATE 行が更新されるときに実行される 使用可能 使用可能
DELETE 行が削除されるときに実行される 使用不可 使用可能


  • INSERTトリガー
    NEWのみが使用可能であり、挿入される新しい値を参照できる。
  • UPDATEトリガー
    NEWとOLDの両方が使用可能であり、変更前の値と変更後の値の両方を参照できる。
  • DELETEトリガー
    OLDのみが使用可能であり、削除される行の値を参照できる。


NEWとOLD疑似レコード

トリガー内では、NEWとOLDという特別な疑似レコードを使用して、変更されるデータにアクセスできる。

NEWとOLD疑似レコード
疑似レコード 説明 使用可能なイベント 書き込み可能
NEW.column_name INSERT / UPDATEで使用する。
新しい値を参照する。
INSERT、UPDATE BEFOREトリガーのみ可能
OLD.column_name UPDATE / DELETEで使用する。
変更前の値を参照する。
UPDATE、DELETE 不可 (読み取り専用)


BEFOREトリガーでは、SET NEW.col_name = value の構文を使用して、挿入または更新される値を変更できる。

NEWとOLDの使用例を以下に示す。

 DELIMITER //
 
 CREATE TRIGGER update_audit BEFORE UPDATE ON products
 FOR EACH ROW
 BEGIN
    -- 価格が変更された場合のみ、変更日時を更新
    IF NEW.price != OLD.price THEN
       SET NEW.updated_at = NOW();
    END IF;
 END//
 
 DELIMITER ;



トリガーの変更と削除

MySQLでは、ALTER TRIGGER 文は存在しない。
トリガーを変更するには、DROP TRIGGER 文で削除してから、CREATE TRIGGER 文で再作成する必要がある。

トリガーを削除するには、DROP TRIGGER 文を使用する。

 DROP TRIGGER [IF EXISTS] [<スキーマ名>.]<トリガー名>;


IF EXISTS 句を使用すると、トリガーが存在しない場合でもエラーにならず、警告のみが出力される。

  • トリガー削除の例
     DROP TRIGGER IF EXISTS users_before_insert;
    

  • スキーマ名を指定してトリガーを削除する例
     DROP TRIGGER IF EXISTS mydb.users_before_insert;
    

  • トリガーを変更するには、削除してから再作成する。
     DROP TRIGGER IF EXISTS users_before_insert;
     
     DELIMITER //
     
     CREATE TRIGGER users_before_insert
     BEFORE INSERT ON users
     FOR EACH ROW
     BEGIN
        SET NEW.email = LOWER(TRIM(NEW.email));
        SET NEW.created_at = NOW();
     END//
     
     DELIMITER ;
    



トリガーの確認

トリガーの情報を確認するには、複数の方法がある。

  • トリガー一覧を確認する。
     SHOW TRIGGERS [FROM <データベース名>] [LIKE 'pattern'];
    

  • 現在のデータベースのトリガー一覧を表示する例
     SHOW TRIGGERS;
    

  • 特定のデータベースのトリガー一覧を表示する例
     SHOW TRIGGERS FROM mydb;
    

  • パターンマッチングで特定のトリガーを検索する例
     SHOW TRIGGERS LIKE 'users%';
    

  • トリガーの定義を確認する。
    SHOW CREATE TRIGGER 文は、トリガーの完全な定義 (CREATE TRIGGER文) を表示する。
     SHOW CREATE TRIGGER <トリガー名>;
    

  • INFORMATION_SCHEMA.TRIGGERS テーブルから詳細情報を取得する。
     SELECT TRIGGER_NAME, EVENT_MANIPULATION, EVENT_OBJECT_TABLE,
            ACTION_STATEMENT, ACTION_TIMING, ACTION_ORDER
     FROM INFORMATION_SCHEMA.TRIGGERS
     WHERE TRIGGER_SCHEMA = '<データベース名>';
    


INFORMATION_SCHEMA.TRIGGERSテーブルの主要カラム
カラム名 説明
TRIGGER_SCHEMA トリガーが定義されているデータベース名
TRIGGER_NAME トリガー名
EVENT_MANIPULATION トリガーイベント (INSERT、UPDATE、DELETE)
EVENT_OBJECT_SCHEMA トリガーが設定されているテーブルのデータベース名
EVENT_OBJECT_TABLE トリガーが設定されているテーブル名
ACTION_ORDER 同一タイミング・同一イベントの複数トリガーがある場合の実行順序
ACTION_STATEMENT トリガーで実行される処理の内容
ACTION_TIMING トリガーのタイミング (BEFORE、AFTER)
CREATED トリガーの作成日時


特定のテーブルに設定されているトリガーを確認する例を以下に示す。

 SELECT TRIGGER_NAME, ACTION_TIMING, EVENT_MANIPULATION, ACTION_ORDER
 FROM INFORMATION_SCHEMA.TRIGGERS
 WHERE EVENT_OBJECT_SCHEMA = 'mydb'
   AND EVENT_OBJECT_TABLE = 'users'
 ORDER BY ACTION_TIMING, EVENT_MANIPULATION, ACTION_ORDER;



トリガーの順序

FOLLOWS句 と PRECEDES句

MySQL 5.7以降では、同一テーブル・同一タイミング・同一イベントの複数トリガーが存在する場合、FOLLOWS句とPRECEDES句を使用して実行順序を制御できる。

FOLLOWS句とPRECEDES句
構文 説明
FOLLOWS other_trigger 指定したトリガー (other_trigger) の後に実行される
PRECEDES other_trigger 指定したトリガー (other_trigger) の前に実行される


FOLLOWS句とPRECEDES句を指定しない場合、最後に作成されたトリガーが最初に実行される (後入れ先出し) 動作になる。

トリガーの実行順序を制御する例を以下に示す。

 DELIMITER //
 
 -- 最初のトリガー
 CREATE TRIGGER users_before_insert_1
 BEFORE INSERT ON users
 FOR EACH ROW
 BEGIN
    SET NEW.email = LOWER(TRIM(NEW.email));
 END//
 
 -- 2番目のトリガー (users_before_insert_1の後に実行)
 CREATE TRIGGER users_before_insert_2
 BEFORE INSERT ON users
 FOR EACH ROW
 FOLLOWS users_before_insert_1
 BEGIN
    SET NEW.created_at = NOW();
 END//
 
 -- 3番目のトリガー (users_before_insert_2の前に実行)
 CREATE TRIGGER users_before_insert_3
 BEFORE INSERT ON users
 FOR EACH ROW
 PRECEDES users_before_insert_2
 BEGIN
    -- 検証処理
    IF NEW.age < 0 OR NEW.age > 150 THEN
       SIGNAL SQLSTATE '45000'
          SET MESSAGE_TEXT = 'Age must be between 0 and 150';
    END IF;
 END//
 
 DELIMITER ;


上記の例では、トリガーの実行順序は以下のようになる。

  1. users_before_insert_1 (最初)
  2. users_before_insert_3 (users_before_insert_2の前)
  3. users_before_insert_2 (users_before_insert_1の後)


ACTION_ORDER カラムで実行順序を確認できる。

 SELECT TRIGGER_NAME, ACTION_TIMING, EVENT_MANIPULATION, ACTION_ORDER
 FROM INFORMATION_SCHEMA.TRIGGERS
 WHERE EVENT_OBJECT_TABLE = 'users'
   AND ACTION_TIMING = 'BEFORE'
   AND EVENT_MANIPULATION = 'INSERT'
 ORDER BY ACTION_ORDER;


ACTION_ORDER カラムの値は、実行順序を示す数値である。
値が小さいほど、先に実行される。


BEFOREトリガーとAFTERトリガーの使い分け

BEFOREトリガーとAFTERトリガーは、用途に応じて使い分ける必要がある。

BEFOREトリガーとAFTERトリガーの使い分け
タイミング 主な用途 特徴
BEFORE
  • データの検証
  • 値の自動変更 / 正規化
  • デフォルト値の設定
  • 制約のチェック
  • SET NEW.col_name = valueで値を変更可能
  • エラーを発生させることで操作を中断可能
AFTER
  • 監査ログの記録
  • 集計テーブルの更新
  • 他のテーブルへのデータ連携
  • 通知の送信
  • NEW / OLDは読み取り専用
  • データ変更後の処理に適している
  • 他のテーブルへの書き込みが可能


BEFOREトリガーは、データが実際にテーブルに書き込まれる前に実行されるため、データの検証や値の変更に適している。
BEFOREトリガー内でSIGNAL文を使用してエラーを発生させることで、操作を中断できる。

AFTERトリガーは、データが既にテーブルに書き込まれた後に実行されるため、監査ログの記録や集計テーブルの更新に適している。
AFTERトリガーでは、NEW / OLD疑似レコードは読み取り専用であり、値を変更できない。

BEFOREトリガーで操作を中断する例を以下に示す。

 DELIMITER //
 
 CREATE TRIGGER validate_age BEFORE INSERT ON users
 FOR EACH ROW
 BEGIN
    IF NEW.age < 0 OR NEW.age > 150 THEN
       SIGNAL SQLSTATE '45000'
          SET MESSAGE_TEXT = 'Age must be between 0 and 150';
    END IF;
 END//
 
 DELIMITER ;


SIGNAL SQLSTATE '45000' は、ユーザー定義例外の汎用コードであり、エラーを発生させて操作を中断する。
MESSAGE_TEXTには、エラーメッセージを指定する。


サンプルクエリ

データ検証

BEFOREトリガーを使用して、データの妥当性を検証する例を以下に示す。

以下の例では、年齢が0未満または150を超える場合にエラーを発生させ、INSERT操作を中断する。

 DELIMITER //
 
 CREATE TRIGGER validate_age BEFORE INSERT ON users
 FOR EACH ROW
 BEGIN
    IF NEW.age < 0 OR NEW.age > 150 THEN
       SIGNAL SQLSTATE '45000'
          SET MESSAGE_TEXT = 'Age must be between 0 and 150';
    END IF;
 END//
 
 DELIMITER ;


複数の条件を検証する例を以下に示す。

 DELIMITER //
 
 CREATE TRIGGER validate_product BEFORE INSERT ON products
 FOR EACH ROW
 BEGIN
    -- 価格の検証
    IF NEW.price <= 0 THEN
       SIGNAL SQLSTATE '45000'
          SET MESSAGE_TEXT = 'Price must be greater than 0';
    END IF;
 
    -- 在庫数の検証
    IF NEW.stock < 0 THEN
       SIGNAL SQLSTATE '45000'
          SET MESSAGE_TEXT = 'Stock cannot be negative';
    END IF;

    -- 商品コードの検証
    IF LENGTH(NEW.product_code) != 8 THEN
       SIGNAL SQLSTATE '45000'
          SET MESSAGE_TEXT = 'Product code must be 8 characters';
    END IF;
 END//
 
 DELIMITER ;


自動値設定

BEFOREトリガーを使用して、値を自動的に設定または正規化する例を以下に示す。

 DELIMITER //
 
 CREATE TRIGGER users_before_insert BEFORE INSERT ON users
 FOR EACH ROW
 BEGIN
    -- メールアドレスを小文字に変換し、前後の空白を削除
    SET NEW.email = LOWER(TRIM(NEW.email));
 
    -- 作成日時を自動設定
    SET NEW.created_at = NOW();
 END//
 
 DELIMITER ;


UPDATEトリガーで更新日時を自動設定する例を以下に示す。

 DELIMITER //
 
 CREATE TRIGGER users_before_update BEFORE UPDATE ON users
 FOR EACH ROW
 BEGIN
    SET NEW.updated_at = NOW();
 END//
 
 DELIMITER ;


条件付きで値を自動設定する例を以下に示す。

 DELIMITER //
 
 CREATE TRIGGER products_before_update BEFORE UPDATE ON products
 FOR EACH ROW
 BEGIN
    -- 価格が変更された場合のみ、価格変更日時を更新
    IF NEW.price != OLD.price THEN
       SET NEW.price_updated_at = NOW();
    END IF;
 END//
 
 DELIMITER ;


監査ログの記録

AFTERトリガーを使用して、データ変更の監査ログを記録する例を以下に示す。

まず、監査ログ用のテーブルを作成する。

 CREATE TABLE audit_log (
    log_id INT AUTO_INCREMENT PRIMARY KEY,
    table_name VARCHAR(100),
    action VARCHAR(10),
    old_values TEXT,
    new_values TEXT,
    changed_by VARCHAR(100),
    changed_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
 );


UPDATEトリガーで変更履歴を記録する例を以下に示す。

 DELIMITER //
 
 CREATE TRIGGER audit_update AFTER UPDATE ON employees
 FOR EACH ROW
 BEGIN
    INSERT INTO audit_log (table_name, action, old_values, new_values, changed_by)
    VALUES ('employees', 'UPDATE',
       CONCAT('name=', OLD.name, ',salary=', OLD.salary),
       CONCAT('name=', NEW.name, ',salary=', NEW.salary),
       USER());
 END//
 
 DELIMITER ;


USER() 関数は、現在接続しているユーザ名を返す。

INSERTとDELETEの監査ログを記録する例を以下に示す。

 DELIMITER //
 
 CREATE TRIGGER audit_insert AFTER INSERT ON employees
 FOR EACH ROW
 BEGIN
    INSERT INTO audit_log (table_name, action, new_values, changed_by)
    VALUES ('employees', 'INSERT',
       CONCAT('id=', NEW.id, ',name=', NEW.name, ',salary=', NEW.salary),
       USER());
 END//
 
 CREATE TRIGGER audit_delete AFTER DELETE ON employees
 FOR EACH ROW
 BEGIN
    INSERT INTO audit_log (table_name, action, old_values, changed_by)
    VALUES ('employees', 'DELETE',
       CONCAT('id=', OLD.id, ',name=', OLD.name, ',salary=', OLD.salary),
       USER());
 END//
 
 DELIMITER ;


集計テーブルの更新

AFTERトリガーを使用して、集計テーブルを自動的に更新する例を以下に示す。

注文テーブルと注文明細テーブルがあり、注文明細が挿入されたときに注文の合計金額を自動更新する。

 CREATE TABLE orders (
    id INT PRIMARY KEY,
    customer_id INT,
    total_amount DECIMAL(10,2) DEFAULT 0.00
 );
 
 CREATE TABLE order_items (
    id INT PRIMARY KEY,
    order_id INT,
    product_id INT,
    quantity INT,
    price DECIMAL(10,2),
    FOREIGN KEY (order_id) REFERENCES orders(id)
 );


注文明細が挿入されたときに、注文の合計金額を更新するトリガーを以下に示す。

 DELIMITER //
 
 CREATE TRIGGER update_order_total AFTER INSERT ON order_items
 FOR EACH ROW
 BEGIN
    UPDATE orders
    SET total_amount = (
       SELECT SUM(quantity * price)
       FROM order_items
       WHERE order_id = NEW.order_id
    )
    WHERE id = NEW.order_id;
 END//
 
 DELIMITER ;


注文明細が更新または削除された時にも、合計金額を更新するトリガーを以下に示す。

 DELIMITER //
 
 CREATE TRIGGER update_order_total_on_update AFTER UPDATE ON order_items
 FOR EACH ROW
 BEGIN
    UPDATE orders
    SET total_amount = (
       SELECT SUM(quantity * price)
       FROM order_items
       WHERE order_id = NEW.order_id
    )
    WHERE id = NEW.order_id;
 END//
 
 CREATE TRIGGER update_order_total_on_delete AFTER DELETE ON order_items
 FOR EACH ROW
 BEGIN
    UPDATE orders
    SET total_amount = (
       SELECT COALESCE(SUM(quantity * price), 0.00)
       FROM order_items
       WHERE order_id = OLD.order_id
    )
    WHERE id = OLD.order_id;
 END//
 
 DELIMITER ;


COALESCE 関数は、最初のNULL以外の値を返す。
全ての注文明細が削除された場合、SUM関数はNULLを返すため、COALESCEで0.00を返すようにしている。


トリガーのデバッグ

トリガーのデバッグは、通常のストアドプロシージャと比べて難しい場合がある。
トリガーは自動的に実行されるため、エラーが発生した場合の原因特定が困難である。

デバッグ用のテーブルを作成して、トリガーの実行をログに記録する方法がある。

 CREATE TABLE trigger_debug_log (
    id INT AUTO_INCREMENT PRIMARY KEY,
    trigger_name VARCHAR(100),
    debug_info TEXT,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
 );


トリガー内にデバッグ情報を記録する例を以下に示す。

 DELIMITER //
 
 CREATE TRIGGER users_before_insert_debug BEFORE INSERT ON users
 FOR EACH ROW
 BEGIN
    -- デバッグ情報を記録
    INSERT INTO trigger_debug_log (trigger_name, debug_info)
    VALUES ('users_before_insert', CONCAT('email=', NEW.email, ',age=', NEW.age));
 
    -- 通常の処理
    SET NEW.email = LOWER(TRIM(NEW.email));
    SET NEW.created_at = NOW();
 END//
 
 DELIMITER ;


開発環境では、general_log を有効にしてクエリログを確認することもできる。

 SET GLOBAL general_log = 1;


general_logファイルの場所を確認する。

 SHOW VARIABLES LIKE 'general_log_file';


本番環境では、general_log はパフォーマンスに影響を与えるため、通常は無効にしておく。


トリガーの制限事項

MySQLのトリガーには、いくつかの制限事項がある。

トリガーの主な制限事項
制限事項 説明
同一テーブルへの変更不可 トリガー内から、トリガーが設定されているテーブルへのINSERT / UPDATE / DELETEは実行できない。
これを行うと、再帰的なトリガー呼び出しが発生し、エラーになる。
COMMIT / ROLLBACK不可 トリガー内で、COMMIT、ROLLBACK、START TRANSACTIONを実行できない。
トリガーは呼び出し元のトランザクション内で実行される。
RETURN文不可 トリガー内でRETURN文を使用できない。
ストアドファンクションとは異なり、トリガーは値を返さない。
VIEWへのトリガー不可 VIEWに対してトリガーを作成できない。
トリガーは、テーブルに対してのみ作成可能である。
LOCK TABLES不可 トリガー内で LOCK TABLES および UNLOCK TABLES を使用できない。
ストアドプロシージャの制限 トリガー内からCALLでストアドプロシージャを呼び出す場合、
そのプロシージャ内でもトリガーと同じ制限が適用される。
直接再帰不可 トリガーは直接再帰呼び出しできない。
ただし、カスケードによる間接的な呼び出しは可能である。
LOAD DATAでのパフォーマンス LOAD DATA文では、BEFOREトリガーが行ごとに実行されるため、
大量データのロード時にパフォーマンスに影響を与える。


トリガー内から同一テーブルを変更しようとすると、エラーが発生する。

 DELIMITER //
 
 -- これはエラーになる
 CREATE TRIGGER invalid_trigger AFTER INSERT ON users
 FOR EACH ROW
 BEGIN
    -- 同一テーブルへのINSERTは不可
    INSERT INTO users (username) VALUES ('test');  -- エラー
 END//
 
 DELIMITER ;


このような場合は、別のテーブルに書き込むか、BEFOREトリガーで値を変更する方法を検討する。

トリガー内でトランザクション制御を行おうとすると、エラーが発生する。

 DELIMITER //
 
 -- これはエラーになる
 CREATE TRIGGER invalid_trigger AFTER INSERT ON users
 FOR EACH ROW
 BEGIN
    START TRANSACTION;  -- エラー
    INSERT INTO audit_log VALUES (...);
    COMMIT;  -- エラー
 END//
 
 DELIMITER ;


トリガーは、呼び出し元のトランザクション内で実行されるため、トランザクション制御は呼び出し元で行う。


関連ページ