実際にデータを変更しないUPDATEステートメントがある場合(データが既に更新された状態にあるため)、更新を防ぐためにwhere句にチェックを入れることでパフォーマンス上の利点はありますか?
UPDATE 1によるわずかなパフォーマンスの違いがあるため、確かに存在する可能性があります。
- 実際に行を更新しない(したがって、ディスクに書き込むものがなく、ログアクティビティが最小限でもない)
- 実際の更新を行うために必要なロックよりも制限の少ないロックを削除します(したがって、同時実行性が向上します)(最後の更新セクションを参照してください)
ただし、システムでスキーマ、データ、およびシステム負荷を使用して、どれだけの差があるかを測定する必要があります。非更新のUPDATEが与える影響に影響するいくつかの要因があります。
- 更新されるテーブルの競合の量
- 更新される行の数
- 更新中のテーブルにUPDATEトリガーがある場合(質問のコメントでマークが指摘したとおり)。を実行する
UPDATE TableName SET Field1 = Field1
と、更新トリガーが起動し、フィールドが更新されたこと(UPDATE()またはCOLUMNS_UPDATED関数を使用して確認した場合)、および両方INSERTED
とDELETED
テーブルのフィールドが同じ値であることを示します。
また、次の要約セクションは、Paul Whiteの記事「更新されない更新の影響」にあります(@spaghettidbaの回答へのコメントに記載されています)。
SQL Serverには、永続データベースへの変更をもたらさないUPDATE操作の処理時に、不要なログやページのフラッシュを回避するための最適化がいくつか含まれています。
- クラスター化されたテーブルへの非更新更新は、クラスターキー(の一部)を形成する列が更新操作の影響を受けない限り、通常、余分なログとページのフラッシュを回避します。
- クラスターキーの一部が同じ値に「更新」されると、操作はデータが変更されたかのように記録され、影響を受けるページはバッファープールでダーティとしてマークされます。これは、UPDATEのdelete-then-insert操作への変換の結果です。
- ヒープテーブルはクラスター化されたテーブルと同じように動作しますが、余分なログやページのフラッシュを発生させるクラスターキーがありません。これは、クラスター化されていない主キーがヒープに存在する場合でも当てはまります。したがって、ヒープの更新が更新されないため、通常は余分なログとフラッシュは回避されます(ただし、以下を参照)。
- ヒープとクラスター化されたテーブルの両方で、8000バイトを超えるデータを含むLOB列が 'SET column_name = column_name'以外の構文を使用して同じ値に更新される行の場合、余分なログとフラッシュが発生します。
- データベースでいずれかのタイプの行バージョン管理分離レベルを有効にすると、常に余分なログとフラッシュが発生します。これは、更新トランザクションに有効な分離レベルに関係なく発生します。
以下の2つの項目を念頭に置いてください(特に、リンクをクリックしてPaulの完全な記事を参照しない場合)。
非更新の更新はまだ持っているいくつかの取引が始まると終了していることを示す、ログの活動を。データの変更が発生しないということだけです(これは依然として大きな節約になります)。
上で述べたように、システムでテストする必要があります。Paulが使用しているものと同じ調査クエリを使用して、同じ結果が得られるかどうかを確認します。私のシステムでは、記事に示されている結果とは少し異なる結果が見られます。書き込まれるダーティページはまだありませんが、もう少しログアクティビティがあります。
...行カウントに未変更の行を含める必要があるため、IDが存在しない場合に挿入を行うかどうかがわかります。...どういうわけか必要な行数を取得することは可能ですか?
単純に、単一の行を処理している場合は、次のことができます。
UPDATE MyTable
SET Value = 2
WHERE ID = 2
AND Value <> 2;
IF (@@ROWCOUNT = 0)
BEGIN
IF (NOT EXISTS(
SELECT *
FROM MyTable
WHERE ID = 2 -- or Value = 2 depending on the scenario
)
)
BEGIN
INSERT INTO MyTable (ID, Value) -- or leave out ID if it is an IDENTITY
VALUES (2, 2);
END;
END;
複数の行の場合、OUTPUT
句を使用して、その決定に必要な情報を取得できます。更新された行を正確にキャプチャすることにより、アイテムを絞り込んで、存在する行を更新しないのと更新を必要としないのに対して、存在しない行を更新しないことの違いを知ることができます。
次の答えに基本的な実装を示します。
xmlパラメータを使用して複数のデータをアップロードするときにMergeクエリを使用しないようにする方法は?
その回答に示されているメソッドは、まだ更新する必要がない既存の行を除外しません。その部分を追加することもできますが、まず、マージするデータセットを取得している場所を正確に示す必要がありますMyTable
。それらは一時テーブルから来ていますか?テーブル値パラメーター(TVP)?
更新1:
私はついにいくつかのテストを行うことができました。ここにトランザクションログとロックに関して見つけたものがあります。まず、テーブルのスキーマ:
CREATE TABLE [dbo].[Test]
(
[ID] [int] NOT NULL CONSTRAINT [PK_Test] PRIMARY KEY CLUSTERED,
[StringField] [varchar](500) NULL
);
次に、フィールドが既に持っている値に更新するテスト:
UPDATE rt
SET rt.StringField = '04CF508B-B78E-4264-B9EE-E87DC4AD237A'
FROM dbo.Test rt
WHERE rt.ID = 4082117
結果:
-- Transaction Log (2 entries):
Operation
----------------------------
LOP_BEGIN_XACT
LOP_COMMIT_XACT
-- SQL Profiler (3 Lock:Acquired events):
Mode Type
--------------------------------------
8 - IX 5 - OBJECT
8 - IX 6 - PAGE
5 - X 7 - KEY
最後に、値が変わらないために更新を除外するテスト:
UPDATE rt
SET rt.StringField = '04CF508B-B78E-4264-B9EE-E87DC4AD237A'
FROM dbo.Test rt
WHERE rt.ID = 4082117
AND rt.StringField <> '04CF508B-B78E-4264-B9EE-E87DC4AD237A';
結果:
-- Transaction Log (0 entries):
Operation
----------------------------
-- SQL Profiler (3 Lock:Acquired events):
Mode Type
--------------------------------------
8 - IX 5 - OBJECT
7 - IU 6 - PAGE
4 - U 7 - KEY
ご覧のとおり、トランザクションの開始と終了をマークする2つのエントリとは対照的に、行を除外するときにトランザクションログには何も書き込まれません。そして、これらの2つのエントリがほとんど何もないことは事実ですが、それらはまだ何かです。
また、変更されていない行をフィルターで除外する場合、PAGEおよびKEYリソースのロックの制限は緩和されます。他のプロセスがこのテーブルと対話していない場合、それはおそらく問題ではありません(しかし、それは実際にどれくらいありそうですか?)。リンクされたブログのいずれかで示されているテスト(および私のテストでさえ)は、テストの一部ではないため、テーブルに競合がないと暗黙的に想定していることに注意してください。更新が行われない更新は非常に軽量であるため、テストは多かれ少なかれ真空状態で行われているため、フィルタリングを行うのにお金を払わなくてもよいと言う。しかし、実稼働環境では、このテーブルは孤立していない可能性が高いです。もちろん、ほんの少しのロギングと、より制限的なロックが、効率の低下につながるわけではないということは、非常によくあることです。この質問に答える最も信頼できる情報源は?SQLサーバー。具体的には:あなたの SQL Serverの。システムに適した方法が表示されます:-)。
更新2:
新しい値が現在の値と同じ(つまり、更新なし)の操作が、新しい値が異なり、更新が必要な操作よりも多い場合、次のパターンは、特にテーブルには多くの競合があります。アイデアはSELECT
、現在の値を取得するために最初に簡単なことをすることです。あなたが値を取得しない場合、あなたはあなたに関する答えを持っていますINSERT
。値がある場合は、単純な操作IF
を行い、必要な場合にUPDATE
のみ発行できます。
DECLARE @CurrentValue VARCHAR(500) = NULL,
@NewValue VARCHAR(500) = '04CF508B-B78E-4264-B9EE-E87DC4AD237A',
@ID INT = 4082117;
SELECT @CurrentValue = rt.StringField
FROM dbo.Test rt
WHERE rt.ID = @ID;
IF (@CurrentValue IS NULL) -- if NULL is valid, use @@ROWCOUNT = 0
BEGIN
-- row does not exist
INSERT INTO dbo.Test (ID, StringField)
VALUES (@ID, @NewValue);
END;
ELSE
BEGIN
-- row exists, so check value to see if it is different
IF (@CurrentValue <> @NewValue)
BEGIN
-- value is different, so do the update
UPDATE rt
SET rt.StringField = @NewValue
FROM dbo.Test rt
WHERE rt.ID = @ID;
END;
END;
結果:
-- Transaction Log (0 entries):
Operation
----------------------------
-- SQL Profiler (2 Lock:Acquired events):
Mode Type
--------------------------------------
6 - IS 5 - OBJECT
6 - IS 6 - PAGE
したがって、取得されるロックは3つではなく2つのみであり、これらのロックは両方ともIntent Sharedであり、Intent eXclusiveまたはIntent Update(Lock Compatibility)ではありません。取得した各ロックも解放されることに留意してください。各ロックは実際には2つの操作であるため、この新しいメソッドは、当初提案されたメソッドの6つの操作ではなく、合計4つの操作です。この操作は15ミリ秒ごとに実行されることを考慮して(およそOPが述べているように)、1秒あたり約66回です。したがって、元の提案は1秒あたり396回のロック/ロック解除操作になりますが、この新しい方法はさらに軽いロックの1秒あたり264回のロック/ロック解除操作になります。これは素晴らしいパフォーマンスを保証するものではありませんが、テストする価値は確かにあります:-)。