データが変更されないUPDATEパフォーマンス


31

UPDATE実際にデータを変更しないステートメントがある場合(データは既に更新された状態にあるため)。WHERE更新を防ぐために節にチェックを入れることでパフォーマンス上の利点はありますか?

たとえば、次のUPDATE 1とUPDATE 2の実行速度に違いがあります。

CREATE TABLE MyTable (ID int PRIMARY KEY, Value int);
INSERT INTO MyTable (ID, Value)
VALUES
    (1, 1),
    (2, 2),
    (3, 3);

-- UPDATE 1
UPDATE MyTable
SET
    Value = 2
WHERE
    ID = 2
    AND Value <> 2;
SELECT @@ROWCOUNT;

-- UPDATE 2
UPDATE MyTable
SET
    Value = 2
WHERE
    ID = 2;
SELECT @@ROWCOUNT;

DROP TABLE MyTable;

私が尋ねる理由は、変更されていない行を含めるために行カウントが必要なので、IDが存在しない場合に挿入を行うかどうかがわかるからです。そのため、UPDATE 2フォームを使用しました。UPDATE 1フォームの使用にパフォーマンス上の利点がある場合、どういうわけか必要な行数を取得することは可能ですか?


sqlperformance.com/2012/10/t-sql-queries/conditional-updatesを参照してください(ただし、値が変更されない場合はプロファイルしませんでした)。
アーロンバートランド

回答:


24

実際にデータを変更しないUPDATEステートメントがある場合(データが既に更新された状態にあるため)、更新を防ぐためにwhere句にチェックを入れることでパフォーマンス上の利点はありますか?

UPDATE 1によるわずかなパフォーマンスの違いがあるため、確かに存在する可能性があります。

  • 実際に行を更新しない(したがって、ディスクに書き込むものがなく、ログアクティビティが最小限でもない)
  • 実際の更新を行うために必要なロックよりも制限の少ないロックを削除します(したがって、同時実行性が向上します)(最後の更新セクションを参照してください

ただし、システムでスキーマ、データ、およびシステム負荷を使用して、どれだけの差があるかを測定する必要があります。非更新のUPDATEが与える影響に影響するいくつかの要因があります。

  • 更新されるテーブルの競合の量
  • 更新される行の数
  • 更新中のテーブルにUPDATEトリガーがある場合(質問のコメントでマークが指摘したとおり)。を実行するUPDATE TableName SET Field1 = Field1と、更新トリガーが起動し、フィールドが更新されたこと(UPDATE()またはCOLUMNS_UPDATED関数を使用して確認した場合)、および両方INSERTEDDELETEDテーブルのフィールドが同じ値であることを示します。

また、次の要約セクションは、Paul Whiteの記事「更新されない更新の影響」にあります(@spaghettidbaの回答へのコメントに記載されています)。

SQL Serverには、永続データベースへの変更をもたらさないUPDATE操作の処理時に、不要なログやページのフラッシュを回避するための最適化がいくつか含まれています。

  • クラスター化されたテーブルへの非更新更新は、クラスターキー(の一部)を形成する列が更新操作の影響を受けない限り、通常、余分なログとページのフラッシュを回避します。
  • クラスターキーの一部が同じ値に「更新」されると、操作はデータが変更されたかのように記録され、影響を受けるページはバッファープールでダーティとしてマークされます。これは、UPDATEのdelete-then-insert操作への変換の結果です。
  • ヒープテーブルはクラスター化されたテーブルと同じように動作しますが、余分なログやページのフラッシュを発生させるクラスターキーがありません。これは、クラスター化されていない主キーがヒープに存在する場合でも当てはまります。したがって、ヒープの更新が更新されないため、通常は余分なログとフラッシュは回避されます(ただし、以下を参照)。
  • ヒープとクラスター化されたテーブルの両方で、8000バイトを超えるデータを含むLOB列が 'SET column_name = column_name'以外の構文を使用して同じ値に更新される行の場合、余分なログとフラッシュが発生します。
  • データベースでいずれかのタイプの行バージョン管理分離レベルを有効にすると、常に余分なログとフラッシュが発生します。これは、更新トランザクションに有効な分離レベルに関係なく発生します。

以下の2つの項目を念頭に置いてください(特に、リンクをクリックしてPaulの完全な記事を参照しない場合)。

  1. 非更新の更新はまだ持っているいくつかの取引が始まると終了していることを示す、ログの活動を。データの変更が発生しないということだけです(これは依然として大きな節約になります)。

  2. 上で述べたように、システムでテストする必要があります。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回のロック/ロック解除操作になります。これは素晴らしいパフォーマンスを保証するものではありませんが、テストする価値は確かにあります:-)。


14

少しズームアウトして、全体像を考えます。現実の世界では、更新ステートメントは本当に次のようになります。

UPDATE MyTable
  SET Value = 2
WHERE
     ID = 2
     AND Value <> 2;

または、次のようになりますか?

UPDATE Customers
  SET AddressLine1 = '123 Main St',
      AddressLine2 = 'Apt 24',
      City = 'Chicago',
      State = 'IL',
      (and a couple dozen more fields)
WHERE
     ID = 2
     AND (AddressLine1 <> '123 Main St'
     OR AddressLine2 <> 'Apt 24'
     OR City <> 'Chicago'
     OR State <> 'IL'
      (and a couple dozen more fields))

実世界では、テーブルには多くの列があるためです。つまり、多くの複雑な動的アプリロジックを生成して動的な文字列を作成する必要があります。または、すべてのフィールドの前後のコンテンツを毎回指定する必要があります。

これらの更新ステートメントをすべてのテーブルに対して動的に作成し、更新されるフィールドのみを渡すと、数年前のNHibernateパラメーターサイズの問題に似たプランキャッシュ汚染の問題にすぐに遭遇する可能性があります。さらに悪いことに、(ストアドプロシージャなどで)SQL Serverで更新ステートメントを作成すると、SQL Serverは文字列を大規模に連結するのにそれほど効率的ではないため、貴重なCPUサイクルを消費します。

これらの複雑さのため、通常、更新を行っているときにこの種の行ごと、フィールドごとの比較を行うことは意味がありません。代わりに、セットベースの操作を考えてください。


1
私の実世界の例はそれと同じくらい簡単ですが、よく呼ばれます。私の見積もりは、ピーク時に15msごとに1回です。SQL Serverが必要のないときにディスクに書き込みをしないように十分に切断できるかどうか疑問に思っていました。
マーティンブラウン

3

行数が大きい場合(ロギングが少なく、ディスクに書き込むダーティページが少ない)にのみ更新する必要のない行をスキップすると、パフォーマンスが向上することがあります。

あなたの場合のように単一行の更新を扱う場合、パフォーマンスの違いは完全に無視できます。すべての場合に行を更新することが簡単になった場合は、実行してください。

トピックの詳細については、Paul Whiteによる非更新アップデートを参照してください。



1

すべてのフィールドの値をチェックする代わりに、関心のある列を使用してハッシュ値を取得し、それをテーブルの行に対して保存されているハッシュと比較することはできませんか?

IF EXISTS (Select 1 from Table where ID =@ID AND HashValue=Sha256(column1+column2))
GOTO EXIT
ELSE
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.