ときどき、特定の列に同じ値を書き込まないように、更新ステートメントを「最適化」しようとする人がいます。私の理解では、行を更新する場合、すべての値が行内にあると仮定すると、行をロックするコストは、その行の1つ、2つ、またはすべての列を更新する増分コストよりもはるかに高くなります。 。
そこで、これをテストするための簡単なテーブルを作成しました:
CREATE TABLE dbo.whatever ( ID INT IDENTITY(1,1) PRIMARY KEY, v1 NVARCHAR(50) NOT NULL, v2 NVARCHAR(50) NOT NULL, v3 NVARCHAR(50) NOT NULL, v4 NVARCHAR(50) NOT NULL, v5 NVARCHAR(50) NOT NULL, v6 NVARCHAR(50) NOT NULL );
次に、さまざまな小さな文字列を含む50,000行をテーブルに入力するストアドプロシージャを作成しました。
CREATE PROCEDURE dbo.clean AS BEGIN SET NOCOUNT ON; TRUNCATE TABLE dbo.whatever; ;WITH x(d) AS ( SELECT d FROM ( VALUES (N'abc'),(N'def'),(N'ghi'), (N'jkl'),(N'mno'),(N'pqr') ) AS y(d) ) INSERT dbo.whatever(v1, v2, v3, v4, v5, v6) SELECT TOP (50000) x1.d, x2.d, x3.d, x4.d, x5.d, x6.d FROM x AS x1, x AS x2, x AS x3, x AS x4, x AS x5, x AS x6, x AS x7; END GO
次に、この変数の割り当てを前提として、特定の列への書き込みを「回避」できる2つの方法で定式化された更新ステートメントを作成しました。
DECLARE @v1 NVARCHAR(50) = N'abc', @v2 NVARCHAR(50) = N'def', @v3 NVARCHAR(50) = N'ghi', @v4 NVARCHAR(50) = N'jkl', @v5 NVARCHAR(50) = N'mno', @v6 NVARCHAR(50) = N'pqr';
まず、CASE式を使用して、列の値が変数の値と同じであるかどうかを確認します。
UPDATE dbo.whatever SET v1 = CASE WHEN v1 <> @v1 THEN @v1 ELSE v1 END, v2 = CASE WHEN v2 <> @v2 THEN @v2 ELSE v2 END, v3 = CASE WHEN v3 <> @v3 THEN @v3 ELSE v3 END, v4 = CASE WHEN v4 <> @v4 THEN @v4 ELSE v4 END, v5 = CASE WHEN v5 <> @v5 THEN @v5 ELSE v5 END, v6 = CASE WHEN v6 <> @v6 THEN @v6 ELSE v6 END WHERE ( v1 <> @v1 OR v2 <> @v2 OR v3 <> @v3 OR v4 <> @v4 OR v5 <> @v5 OR v6 <> @v6 );
次に、列ごとに独立したUPDATEを発行します(それぞれ、その値が実際に変更された行のみを対象とします):
UPDATE dbo.whatever SET v1 = @v1 WHERE v1 <> @v1; UPDATE dbo.whatever SET v2 = @v2 WHERE v2 <> @v2; UPDATE dbo.whatever SET v3 = @v3 WHERE v3 <> @v3; UPDATE dbo.whatever SET v4 = @v4 WHERE v4 <> @v4; UPDATE dbo.whatever SET v5 = @v5 WHERE v5 <> @v5; UPDATE dbo.whatever SET v6 = @v6 WHERE v6 <> @v6;
次に、これを今日のほとんどの方法と比較します。特定の列の既存の値であるかどうかを気にせずに、すべての列を更新するだけです。
UPDATE dbo.whatever SET v1 = @v1, v2 = @v2, v3 = @v3, v4 = @v4, v5 = @v5, v6 = @v6 WHERE ( v1 <> @v1 OR v2 <> @v2 OR v3 <> @v3 OR v4 <> @v4 OR v5 <> @v5 OR v6 <> @v6 );
(これらはすべて、列とパラメーター/変数がNULL可能ではないことを前提としています。その場合、どちらかの側でNULLを比較するために、COALESCEを使用する必要があります。また、追加のWHERE句があることを前提としています。特定の行をターゲットにする–この例では、すべてを網羅するWHERE句なしで1番目と3番目のクエリを実行し、ほぼ同じ結果を確認できます。簡潔にするために、これは単純にしています。)
次に、これらの3つのケースで、値が変更される可能性がある場合、特定の値が変更される可能性がある場合、値が変更されない場合、およびすべての値が変更される場合に何が起こるかを確認したいと思いました。特定の列に定数を挿入するようにストアドプロシージャを変更するか、変数の割り当て方法を変更することで、これに影響を与えることができます。
-- to show when any value might change in a row, the procedure uses the full cross join: SELECT TOP (50000) x1.d, x2.d, x3.d, x4.d, x5.d, x6.d -- to show when particular values will change on many rows, we can hard-code constants: -- two values exempt: SELECT TOP (50000) N'abc', N'def', x3.d, x4.d, x5.d, x6.d -- four values exempt: SELECT TOP (50000) N'abc', N'def', N'ghi', N'jkl', x5.d, x6.d -- to show when no values will change, we hard-code all six values: SELECT TOP (50000) N'abc', N'def', N'ghi', N'jkl', N'mno', N'pqr' -- and to show when all values will change, a different variable assignment would take place: DECLARE @v1 NVARCHAR(50) = N'zzz', @v2 NVARCHAR(50) = N'zzz', @v3 NVARCHAR(50) = N'zzz', @v4 NVARCHAR(50) = N'zzz', @v5 NVARCHAR(50) = N'zzz', @v6 NVARCHAR(50) = N'zzz';
結果
これらのテストを実行した後、すべてのシナリオで「ブラインドアップデート」が勝ちました。今、あなたは考えています、数百ミリ秒は何ですか?外挿します。システムで多くの更新を実行している場合、これは実際に負担をかけ始める可能性があります。
プランエクスプローラーでの詳細な結果:変更| 2つの値が免除されます| 4つの値が免除されます|すべての値が免除されます|すべての変更
Rojiからのフィードバックに基づいて、これもいくつかのインデックスでテストすることにしました。
CREATE INDEX x1 ON dbo.whatever(v1); CREATE INDEX x2 ON dbo.whatever(v2); CREATE INDEX x3 ON dbo.whatever(v3) INCLUDE(v4,v5,v6);
これらのインデックスにより、期間が大幅に延長されました:
プランエクスプローラーでの詳細な結果:変更| 2つの値が免除されます| 4つの値が免除されます|すべての値が免除されます|すべての変更
結論
このテストから、値を更新する必要があるかどうかを確認する価値は通常ないように思われます。 UPDATEステートメントが複数の列に影響を与える場合、各列を個別にチェックするよりも、値が変更された可能性のあるすべての列をスキャンする方がほとんどの場合安価です。将来の投稿では、このシナリオがLOB列で並列化されているかどうかを調査します。