2026年1月16日金曜日

SQL Server トリガーの仕組みと実践活用|AFTER/INSTEAD OF・監査・副作用対策

トリガーで“自動化と整合性”をデータ層に実装する

SQL Serverのトリガー(Trigger)は、テーブルやビューへのINSERT/UPDATE/DELETEなどの操作を契機に自動実行される仕組みです。
アプリケーション層に依存せず、整合性チェック・履歴管理・監査ログを実現できる一方、誤用するとパフォーマンスや可読性を損なうこともあります。
本記事では、AFTER/INSTEAD OFトリガーの違い・実装例・副作用防止策・ベストプラクティスを体系的に解説します。

目次

第1章 トリガーの基本構造と種類

トリガーは特定のデータ操作(DML:INSERT/UPDATE/DELETE)やDDL操作(CREATE/ALTERなど)に反応して自動実行される仕組みです。
特に業務ロジックの自動化や履歴保持に用いられるのはDMLトリガーです。

  • AFTERトリガー: 対象操作が完了した後に実行(整合性チェック・ログ記録に最適)
  • INSTEAD OFトリガー: 対象操作の代わりに実行(ビュー上の更新などに利用)
  • DDLトリガー: CREATE TABLE/ALTER DATABASE などスキーマ変更を検知
-- 基本構文
CREATE TRIGGER トリガー名
ON テーブル名
AFTER INSERT, UPDATE, DELETE
AS
BEGIN
  -- 処理内容
END;

トリガー内では、直前の変更データを参照するために inserteddeleted という仮想テーブルが利用できます。

第2章 AFTERトリガーの動作と活用例

AFTERトリガーは、対象操作が完了した後に発火します。 もっとも一般的で、整合性チェックや監査ログ記録などに最適です。

CREATE TABLE dbo.Customers (
  CustomerID INT PRIMARY KEY,
  Name NVARCHAR(50),
  UpdatedAt DATETIME
);
GO

CREATE TABLE dbo.CustomerLog (
  LogID INT IDENTITY PRIMARY KEY,
  CustomerID INT,
  Action NVARCHAR(10),
  ChangedAt DATETIME
);
GO

CREATE TRIGGER trg_AfterUpdate_Customer
ON dbo.Customers
AFTER UPDATE
AS
BEGIN
  INSERT INTO dbo.CustomerLog (CustomerID, Action, ChangedAt)
  SELECT i.CustomerID, 'UPDATE', SYSDATETIME()
  FROM inserted AS i;
END;

この例では、顧客テーブルが更新されるたびに自動的にログが追記されます。 アプリ側の変更漏れを防ぎ、監査要件をデータベース層で担保できます。

第3章 INSTEAD OFトリガーの仕組みと使いどころ

INSTEAD OFトリガーは、指定された操作の「代わり」に実行されます。
主にビューへのINSERT/UPDATE/DELETEを有効化するために使われます。

CREATE VIEW dbo.vw_ActiveOrders AS
SELECT OrderID, CustomerID, Status
FROM dbo.Orders
WHERE Status = 'Active';
GO

CREATE TRIGGER trg_InsteadOfDelete_vwOrders
ON dbo.vw_ActiveOrders
INSTEAD OF DELETE
AS
BEGIN
  UPDATE dbo.Orders
  SET Status = 'Canceled'
  WHERE OrderID IN (SELECT d.OrderID FROM deleted AS d);
END;

このようにビュー上で削除操作を行っても、実際にはキャンセル扱いとして更新処理に置き換わります。 業務ルールをSQL層で保証する設計パターンとして有効です。

第4章 監査・履歴管理トリガーの実装

トリガーの代表的な用途は履歴管理(監査ログ)です。 変更履歴を保持することで、追跡・再現・復旧が容易になります。

CREATE TABLE dbo.Sales (
  SalesID INT PRIMARY KEY,
  Amount MONEY,
  UpdatedAt DATETIME
);
GO

CREATE TABLE dbo.SalesHistory (
  HistoryID INT IDENTITY PRIMARY KEY,
  SalesID INT,
  OldAmount MONEY,
  NewAmount MONEY,
  ChangedAt DATETIME
);
GO

CREATE TRIGGER trg_AfterUpdate_Sales
ON dbo.Sales
AFTER UPDATE
AS
BEGIN
  INSERT INTO dbo.SalesHistory (SalesID, OldAmount, NewAmount, ChangedAt)
  SELECT d.SalesID, d.Amount, i.Amount, SYSDATETIME()
  FROM deleted AS d
  JOIN inserted AS i ON d.SalesID = i.SalesID;
END;

この設計により、更新のたびに前後データが履歴テーブルに保存されます。 アプリ側で特別な実装をしなくても、完全な変更ログを残せます。

第5章 副作用・多重発火を防ぐ設計指針

トリガーの最大の落とし穴は副作用です。 トリガー内で別テーブルを更新し、そのテーブルにもトリガーが存在する場合、連鎖的に発火して意図しない再帰処理が発生します。

  • SET NOCOUNT ON:メッセージ出力を抑制し、再発火防止。
  • 再帰抑止:ALTER DATABASE SET RECURSIVE_TRIGGERS OFF
  • 自己検知:実行中セッションや処理回数を確認して多重実行を防ぐ。
  • 性能監視:拡張イベント sqlserver.trigger_execution で発火状況を分析。
ALTER DATABASE CURRENT SET RECURSIVE_TRIGGERS OFF;
GO

-- 自己再発防止例
CREATE TRIGGER trg_PreventRecurse
ON dbo.Example
AFTER UPDATE
AS
BEGIN
  IF TRIGGER_NESTLEVEL() > 1 RETURN;
  -- 本来の処理
END;

トリガーの使用範囲は最小限に留め、必ず「監査・整合性保持」など限定目的で利用することが推奨されます。

まとめ:トリガーを安全に使うルール

トリガーはアプリ層に依存せず自動処理を行える強力な仕組みですが、 乱用すると副作用や性能問題を招きます。
目的を明確にし、単一責務・再帰防止・ログ監査設計を徹底すれば、 信頼性と保守性を両立した自動化基盤を構築できます。

参考リンク(公式ドキュメント)

SQL Server 解説用イメージ

0 件のコメント:

コメントを投稿