特定の列が特定の値から他の値に変更されるのを監視するテーブルにUPDATEトリガーがあります。これが発生すると、単一のUPDATEステートメントを使用して、別のテーブルの関連データを更新します。
トリガーが最初に行うことは、更新された行にこの列の値が問題の値から変更されているかどうかを確認することです。単にINSERTEDをDELETEDに結合し、その列の値を比較します。適格なものがなければ、早期にベイルアウトするため、UPDATEステートメントは実行されません。
IF NOT EXISTS (
SELECT TOP 1 i.CUSTNMBR
FROM INSERTED i
INNER JOIN DELETED d
ON i.CUSTNMBR = d.CUSTNMBR
WHERE d.CUSTCLAS = 'Misc'
AND i.CUSTCLAS != 'Misc'
)
RETURN
この場合、CUSTNMBRは基礎となるテーブルの主キーです。このテーブルで大規模な更新(たとえば、5000行以上)を実行すると、CUSTCLAS列に触れていなくても、このステートメントはAGESになります。プロファイラーでこのステートメントが数分間停止するのを見ることができます。
実行計画は奇妙です。挿入されたスキャンが3,714回実行され、出力行が約1,850万行あります。 CUSTCLAS列のフィルターを通過します。これは(ネストされたループを介して)削除されたスキャン(これもCUSTCLASでフィルター処理されます)に結合されます。このスキャンは1回だけ実行され、5000出力行を持ちます。
これを引き起こすために私はここでどんな馬鹿げたことをしていますか?トリガーは絶対に複数行の更新を適切に処理する必要があることに注意してください。
[〜#〜]編集[〜#〜]:
私もこのように書いてみました(EXISTSが不愉快なことをしている場合に備えて)、それでも同じくらいひどいです。
DECLARE @CUSTNMBR varchar(31)
SELECT TOP 1 @CUSTNMBR = i.CUSTNMBR
FROM INSERTED i
INNER JOIN DELETED d
ON i.CUSTNMBR = d.CUSTNMBR
WHERE d.CUSTCLAS = 'Misc'
AND i.CUSTCLAS != 'Misc'
IF @CUSTNMBR IS NULL
RETURN
明示的な_INNER MERGE JOIN
_または_INNER HASH JOIN
_ヒントを使用して評価することもできますが、おそらくこれらのテーブルをトリガーの後半で再度使用している場合、inserted
およびdeleted
テーブルの内容をインデックス付き_#temp
_テーブルとそれで終わりです。
それらは自動的に作成される有用なインデックスを取得しません。
私はこれが答えられたことを知っていますが、最近アクティブになってポップアップしただけで、何百万もの行があるテーブルでもこれに遭遇しました。受け入れられた回答を無視するわけではありませんが、同様のテスト(1つ以上の列の値が実際に変更されているかどうかを確認する)を行うときのトリガーパフォーマンスの重要な要素は列かどうかであるという経験から、テストされているのは、実際にはUPDATE
ステートメントの一部です。実際にinserted
ステートメントの一部であるnot部分であるdeleted
テーブルとUPDATE
テーブルの間の列を比較すると、これらのフィールドがUPDATE
ステートメント(実際に変更される値に関係なく)。それらの列のいずれかが変更されている可能性を論理的に排除できる場合、変更があったかどうかを判断するためにそのすべての作業(つまり、X行のNフィールドを比較するクエリ)を行う理由(それらが存在しない場合は明らかに不可能) SET
ステートメントのUPDATE
句内。
私が採用した解決策は、トリガー内でのみ機能する PDATE() 関数を使用することでした。この組み込み関数は、UPDATE
ステートメントで列が指定されているかどうかを通知し、関係する列がUPDATE
の一部でない場合にトリガーを終了するために使用できます。これをSELECT
と組み合わせて使用すると、それらの列がUPDATE
にあると想定して、実際の変更があるかどうかを判断できます。いくつかの監査トリガーの上部に次のようなコードがあります。
_-- exit on updates that do not update the only 3 columns we ETL
IF (
EXISTS(SELECT 1 FROM DELETED) -- this is an UPDATE (Trigger is AFTER INSERT, UPDATE)
AND (
NOT (UPDATE(Column3) OR UPDATE(Column7)
OR UPDATE(Column11)) -- the columns we care about are not being updated
OR NOT EXISTS(
SELECT 1
FROM INSERTED ins
INNER JOIN DELETED del
ON del.KeyField1 = ins.KeyField1
AND del.KeyField2 = ins.KeyField2
WHERE ins.Column3 <> del.Column3
COLLATE Latin1_General_100_CS_AS -- case-sensitive compare
OR ISNULL(ins.Column7, -99) <>
ISNULL(del.Column7, -99) -- NULLable INT field
OR ins.[Column11] <> del.[Column11] -- NOT NULL INT field
)
)
)
BEGIN
RETURN;
END;
_
このロジックは、次の場合にトリガーの残りの部分に進みます。
INSERT
ですSET
のUPDATE
句にありおよび1つの行のそれらの列の少なくとも1つが変更されているNOT (UPDATE...) OR NOT EXISTS()
は奇妙に見えたり、逆に見えるかもしれませんが、関連する列がSELECT
の一部ではない場合、inserted
およびdeleted
テーブルでUPDATE
を実行しないように設計されています。
必要に応じて、 COLUMNS_UPDATED() 関数は、どの列がUPDATE
ステートメントの一部であるかを決定する別のオプションです。
存在する場合は、
IF EXISTS (SELECT TOP 1 i.CUSTNMBR
FROM INSERTED i
INNER JOIN DELETED d
ON i.CUSTNMBR = d.CUSTNMBR and d.custclass = 'Misc'
WHERE d.CUSTCLAS <>i.CUSTCLAS)
BEGIN
--do your triggerstuff here
END
http://dave.brittens.org/blog/writing-well-behaved-triggers.html
Daveによると、仮想INSERTED/DELETEDテーブルには何もないため、一時テーブルまたはインデックス付きのテーブル変数を使用する必要があります。再帰的なトリガーの可能性がある場合は、テーブルの変数を使用して名前の衝突を回避する必要があります。
元の投稿がかなり前にあったため、誰かがこれが役立つことを願っています...