クエリのコンパイルおよび実行中、SQL Server は、UPDATE ステートメントが実際に値を変更するかどうかを判断するのに時間をかけません。不要であっても、期待どおりに書き込みを実行するだけです。
のようなシナリオで
update table1 set col1 = 'hello'
SQL は何もしないと思うかもしれませんが、実際に値を変更したかのように必要なすべての書き込みを実行します。これは、物理テーブル (またはクラスター化インデックス) と、その列に定義された非クラスター化インデックスの両方で発生します。これにより、物理テーブル/インデックスへの書き込み、インデックスの再計算、およびトランザクション ログの書き込みが発生します。大規模なデータ セットを操作する場合、変更を受け取る行のみを更新すると、パフォーマンスが大幅に向上します。
必要のないときにこれらの書き込みのオーバーヘッドを回避したい場合は、更新の必要性をチェックする方法を考案する必要があります。更新の必要性を確認する 1 つの方法は、「where col <> 'hello'.
」のようなものを追加することです。update table1 set col1 = 'hello' where col1 <> 'hello'
ただし、これは場合によってはうまく機能しません。たとえば、多くの行を持つテーブルの複数の列を更新していて、それらの行の小さなサブセットのみが実際に値を変更する場合です。これは、これらすべての列をフィルタリングする必要があるためです。非等値述語は通常、インデックス シークを使用できず、上記のテーブルとインデックスの書き込みとトランザクション ログ エントリのオーバーヘッドが発生します。
しかし、EXISTS 句と EXCEPT 句を組み合わせて使用する、はるかに優れた代替手段があります。目的は、宛先行の値を一致するソース行の値と比較して、更新が実際に必要かどうかを判断することです。以下の変更されたクエリを見て、EXISTS で始まる追加のクエリ フィルターを調べます。 EXISTS 句の内部では、SELECT ステートメントに FROM 句がないことに注意してください。この部分は特に重要です。なぜなら、クエリ プランに追加の定数スキャンとフィルター操作が追加されるだけだからです (両方のコストはわずかです)。最終的に得られるのは、UPDATE がそもそも必要かどうかを判断するための非常に軽量な方法であり、不必要な書き込みオーバーヘッドを回避します。
update table1 set col1 = 'hello'
/* AVOID NET ZERO CHANGES */
where exists
(
/* DESTINATION */
select table1.col1
except
/* SOURCE */
select col1 = 'hello'
)
これは、テーブル内のすべての行の 1 つの値をリテラル値で更新しているときに、元の質問の単純なシーンの単純な WHERE 句で更新をチェックするのに対して、非常に複雑に見えます。ただし、この手法は、テーブル内の複数の列を更新していて、更新のソースが別のクエリであり、書き込みとトランザクション ログのエントリを最小限に抑えたい場合に非常にうまく機能します。また、すべてのフィールドを <> でテストするよりも優れたパフォーマンスを発揮します。
より完全な例は
update table1
set col1 = 'hello',
col2 = 'hello',
col3 = 'hello'
/* Only update rows from CustomerId 100, 101, 102 & 103 */
where table1.CustomerId IN (100, 101, 102, 103)
/* AVOID NET ZERO CHANGES */
and exists
(
/* DESTINATION */
select table1.col1
table1.col2
table1.col3
except
/* SOURCE */
select z.col1,
z.col2,
z.col3
from #anytemptableorsubquery z
where z.CustomerId = table1.CustomerId
)