-
Notifications
You must be signed in to change notification settings - Fork 0
MS_SQLServerTrigger
- 戻る(SQL Server)(SQL Server のクエリ)
- SQL Server のトリガ
- SQL Server のオプティマイザ / SQL Server のコネクションとセッション
- トリガには DDL トリガと DML トリガがある。
- トリガの他にイベント通知と言うものがある。
-
DML トリガ
https://learn.microsoft.com/ja-jp/sql/relational-databases/triggers/dml-triggers-
DML トリガの種類
https://learn.microsoft.com/ja-jp/sql/relational-databases/triggers/dml-triggers- AFTER トリガ
- INSTEAD OF トリガ
-
DML トリガのプランニング ガイドライン
https://learn.microsoft.com/ja-jp/sql/relational-databases/triggers/create-dml-triggers
-
-
DDL トリガ
https://learn.microsoft.com/ja-jp/sql/relational-databases/triggers/ddl-triggers -
DDL イベント
https://learn.microsoft.com/ja-jp/sql/relational-databases/triggers/ddl-events -
DML イベント(存在しない)
補足(3種類目のトリガ): SQL Server には
ログオン トリガもある(CREATE TRIGGER ... ON ALL SERVER FOR LOGON)。
接続元 IP や時間帯による接続制御に使えるが、
トリガ内でエラーが起きると誰もログオンできなくなるため、
使用する場合は DAC(専用管理者接続)で復旧できるよう準備しておくこと。
-
トリガーが同期なのに対しコチラは非同期(キュー)。
-
原則としてイベント発生元の処理を直接ブロックしない。
-
イベント通知
https://learn.microsoft.com/ja-jp/sql/relational-databases/service-broker/event-notifications
-
@IT:テーブルで複数の処理を実行させるトリガー
http://www.atmarkit.co.jp/fnetwork/rensai/sql24/sql1.html -
SqlServer の SQL で Sleep させる方法 - プログラマ備忘録
http://it.emise.net/?p=77
EXEC sp_addmessage
@msgnum = 50001,
@severity = 10,
@msgtext = 'Trigger Executed',
@lang = 'us_english',
@with_log = true,
--@replace= 'replace'CREATE TRIGGER trNotifyCustomerUpdate ON Customers
FOR INSERT, UPDATE, DELETE
AS
BEGIN
WAITFOR DELAY '00:01:00';
RAISERROR (50001,10,10);
ENDUPDATE Customers SET Country = 'USA' WHERE Country = 'USA'(13 行処理されました)
↓
13 行が対象となってもトリガ起動は 1 回だけの模様。
#処理時間 1 分、ログ出力 1 回
補足(この検証が示していること): これは DML トリガの最重要の性質、
「トリガは行単位ではなくステートメント単位で 1 回だけ起動する」
を実測で確認したもの。
Oracle の行レベル トリガ(FOR EACH ROW)に相当する機能は
SQL Server には無く、複数行の処理は
後述のinserted/deletedテーブルを集合として扱う必要がある。同時に、トリガ内の
WAITFOR DELAY '00:01:00'によって
UPDATE全体が 1 分間ブロックされている点も重要で、
トリガは呼び出し元と同じトランザクションで同期実行されることを示している。
重い処理をトリガに入れると、そのままロック保持時間の延長になる
(SQL Server でのロック・タイムアウト参照)。
トリガではなく、イベント通知を使用します。
(ただし、DML イベントは存在せず、DDL イベントのみ存在する)
- イベント通知とトリガについて
https://learn.microsoft.com/ja-jp/sql/relational-databases/service-broker/event-notifications- トリガを起動させるトランザクションのスコープ内で同期的に処理されます。
- イベント通知を発生させるトランザクションのスコープ外で非同期に処理される。
補足(DML を非同期にしたい場合): DML イベント通知が無いため、
更新を非同期に伝搬したい場合は以下のいずれかを採る。
方式 概要 Outbox パターン トリガ(またはアプリ)で同一トランザクション内に「送信待ちテーブル」へ行を挿入し、別プロセスが拾って処理する Service Broker トリガ内で SENDしてキューに投入し、アクティベーション プロシージャで非同期処理変更追跡 / CDC CHANGE_TRACKINGや Change Data Capture で変更を後から取得するいずれも「トリガ内で長時間の処理をしない」ための仕組みである。
複数行更新された後の処理については注意が必要。
詳しくは、以下のページを参照のこと。
DML トリガの複数行に関する注意点
https://learn.microsoft.com/ja-jp/sql/relational-databases/triggers/create-dml-triggers-to-handle-multiple-rows-of-data
-
@@ROWCOUNT関数を使用する。
@@ROWCOUNT(Transact-SQL)
https://learn.microsoft.com/ja-jp/sql/t-sql/functions/rowcount-transact-sql -
insertedテーブルとdeletedテーブルを使用する(↓参照)。
-
insertedテーブルには、
INSERTステートメント、UPDATEステートメントの
実行で影響を受けた行のコピーが格納されます。 -
deletedテーブルには、
DELETEステートメント、UPDATEステートメントの
実行で影響を受けた行のコピーが格納されます。
詳しくは、以下のページを参照のこと。
inserted テーブルと deleted テーブルの使用
https://learn.microsoft.com/ja-jp/sql/relational-databases/triggers/use-the-inserted-and-deleted-tables
補足(
inserted/deletedは tempdb 上): この 2 つの疑似テーブルは
tempdb のバージョン ストアに作られる。このため、
- 大量行を一括更新するとトリガの実行が tempdb を圧迫する
- インデックスが張れないため、大量行との結合は遅くなりうる
大量件数を扱うトリガでは、いったん
インデックス付きの一時テーブルに退避する手もある。
-- Trigger valid for multirow and single row inserts
-- and optimal for single row inserts.
USE AdventureWorks2012;
GO
CREATE TRIGGER NewPODetail3
ON Purchasing.PurchaseOrderDetail
FOR INSERT AS
IF @@ROWCOUNT = 1
BEGIN
-- 1行の時はLineTotalをそのまま
-- Purchasing.PurchaseOrderHeaderに足し込む。
UPDATE Purchasing.PurchaseOrderHeader
SET SubTotal = SubTotal + LineTotal
FROM inserted
WHERE PurchaseOrderHeader.PurchaseOrderID = inserted.PurchaseOrderID
END
ELSE
BEGIN
-- 複数行の時は
-- inserted.LineTotalを合計して
-- Purchasing.PurchaseOrderHeaderに足し込む。
-- WHERE句にはIN句にinserted.PurchaseOrderIDを指定し順次処理する。
UPDATE Purchasing.PurchaseOrderHeader
SET SubTotal = SubTotal +
(SELECT SUM(LineTotal)
FROM inserted
WHERE PurchaseOrderHeader.PurchaseOrderID
= inserted.PurchaseOrderID)
WHERE PurchaseOrderHeader.PurchaseOrderID IN
(SELECT PurchaseOrderID FROM inserted)
END補足(
@@ROWCOUNTを使う前にSET NOCOUNT ONを置かない):
トリガの先頭にSET NOCOUNT ON;を書くのは一般に推奨されるが、
上記のように@@ROWCOUNTで分岐する実装では順序に注意が必要。
SET NOCOUNT ON自体は@@ROWCOUNTを 0 にリセットするため、
@@ROWCOUNTを参照するなら参照後に置くか、
そもそも分岐せず複数行対応のクエリ 1 本にまとめるのが安全である。
上の例も、ELSE側のクエリだけで 1 行の場合も正しく動作する。
補足(トリガを使う前に検討すること): トリガは
「呼び出し元から見えない場所で動く」ため、
障害解析を著しく難しくする。以下で代替できないか先に検討する。
目的 代替 集計列の維持 計算列( PERSISTED)、インデックス付きビュー履歴の記録 システム バージョン管理されたテンポラル テーブル(2016 以降) 監査 SQL Server Audit、変更データ キャプチャ(CDC) 既定値・整合性 既定値制約、 CHECK制約、外部キー更新結果の取得 OUTPUT句それでもトリガが必要な場合は、
- 必ず複数行を前提に書く(カーソルを使わない)
- トリガ内でエラーが起きた場合の挙動(
XACT_ABORT、ロールバック)を決める- トリガの中で別テーブルを更新してトリガが連鎖しないか確認する
(nested triggers設定、再帰トリガRECURSIVE_TRIGGERS)を守ること。
Tags: 移行, データアクセス, SQL Server
このWikiは「Open棟梁Project」,「OSSコンソーシアム 開発基盤部会」によって運営されています。