SQL Serverは、複数の行に影響する(または影響する可能性のある)更新の一部として一意のインデックスを維持する場合、常に演算子の分割、並べ替え、および折りたたみの組み合わせを使用します。
質問の例を使用して、存在する4行ごとに個別の単一行更新として更新を記述できます。
-- Per row updates
UPDATE dbo.Banana SET pk = 2 WHERE pk = 1;
UPDATE dbo.Banana SET pk = 3 WHERE pk = 2;
UPDATE dbo.Banana SET pk = 4 WHERE pk = 3;
UPDATE dbo.Banana SET pk = 5 WHERE pk = 4;
それは変化するので問題は、最初のステートメントが失敗することがあるpk
1から2まで、及び行既に存在するpk
= 2ザSQL Serverストレージエンジンは一意のインデックスがあっても、単一のステートメント内、処理の各段階で一意のままである必要が。これは、分割、並べ替え、および折りたたみによって解決される問題です。
スプリット
最初のステップは、各更新ステートメントを削除に続いて挿入に分割することです。
DELETE dbo.Banana WHERE pk = 1;
INSERT dbo.Banana (pk, c1, c2) VALUES (2, 'A', 'W');
DELETE dbo.Banana WHERE pk = 2;
INSERT dbo.Banana (pk, c1, c2) VALUES (3, 'B', 'X');
DELETE dbo.Banana WHERE pk = 3;
INSERT dbo.Banana (pk, c1, c2) VALUES (4, 'C', 'Y');
DELETE dbo.Banana WHERE pk = 4;
INSERT dbo.Banana (pk, c1, c2) VALUES (5, 'D', 'Z');
Splitオペレーターは、アクションコード列をストリームに追加します(ここではAct1007とラベル付けされています)。
アクションコードは、更新の場合は1、削除の場合は3、挿入の場合は4です。
ソート
上記の分割ステートメントは依然として一時的な一意のキー違反を生成するため、次のステップは、更新される一意のインデックスのキー(pk
この場合)、アクションコードの順にステートメントを並べ替えることです。この例では、これは単に、同じキーの削除(3)が挿入(4)の前に順序付けられることを意味します。結果の順序は次のとおりです。
-- Sort (pk, action)
DELETE dbo.Banana WHERE pk = 1;
DELETE dbo.Banana WHERE pk = 2;
INSERT dbo.Banana (pk, c1, c2) VALUES (2, 'A', 'W');
DELETE dbo.Banana WHERE pk = 3;
INSERT dbo.Banana (pk, c1, c2) VALUES (3, 'B', 'X');
DELETE dbo.Banana WHERE pk = 4;
INSERT dbo.Banana (pk, c1, c2) VALUES (4, 'C', 'Y');
INSERT dbo.Banana (pk, c1, c2) VALUES (5, 'D', 'Z');
崩壊
前の段階は、すべての場合において誤った一意性違反の回避を保証するのに十分です。最適化として、Collapseは同じキー値の隣接する削除と挿入を更新に結合します。
-- Collapse (pk)
DELETE dbo.Banana WHERE pk = 1;
UPDATE dbo.Banana SET c1 = 'A', c2 = 'W' WHERE pk = 2;
UPDATE dbo.Banana SET c1 = 'B', c2 = 'X' WHERE pk = 3;
UPDATE dbo.Banana SET c1 = 'C', c2 = 'Y' WHERE pk = 4;
INSERT dbo.Banana (pk, c1, c2) VALUES (5, 'D', 'Z');
pk
値2、3、および4 の削除/挿入ペアは更新に結合され、pk
1 つの削除= 1、およびpk
= 5の挿入を残しています。
Collapse演算子は、キー列で行をグループ化し、アクションコードを更新して、折りたたみの結果を反映します。
クラスター化インデックスの更新
この演算子には「更新」というラベルが付いていますが、挿入、更新、削除が可能です。行ごとにクラスター化インデックス更新によって実行されるアクションは、その行のアクションコードの値によって決まります。オペレーターには、この操作モードを反映するActionプロパティがあります。
行修正カウンター
上記の3つの更新は、保持されている一意のインデックスのキーを変更しないことに注意してください。実際には、インデックス内のキー列の更新を、非キー列(c1
およびc2
)の更新に加えて、削除と挿入に変換しました。削除も挿入も、誤った一意キー違反を引き起こすことはありません。
挿入または削除は、行内のすべての列に影響を与えるため、すべての列に関連付けられた統計の修正カウンターは増分されます。更新の場合、(値が変更されていない場合でも)更新された列のいずれかが先行列である統計のみの変更カウンターが増分されます。
したがって、統計行変更カウンターは、に2つの変更を表示しpk
、c1
とc2
:
-- Collapse (pk)
DELETE dbo.Banana WHERE pk = 1; -- All columns modified
UPDATE dbo.Banana SET c1 = 'A', c2 = 'W' WHERE pk = 2; -- c1 and c2 modified
UPDATE dbo.Banana SET c1 = 'B', c2 = 'X' WHERE pk = 3; -- c1 and c2 modified
UPDATE dbo.Banana SET c1 = 'C', c2 = 'Y' WHERE pk = 4; -- c1 and c2 modified
INSERT dbo.Banana (pk, c1, c2) VALUES (5, 'D', 'Z'); -- All columns modified
注意:ベースオブジェクト(ヒープまたはクラスター化インデックス)に適用される変更のみが、統計行の変更カウンターに影響します。非クラスター化インデックスは、ベースオブジェクトに既に加えられた変更を反映する二次構造です。これらは統計行変更カウンターにはまったく影響しません。
オブジェクトに複数の一意のインデックスがある場合、個別の分割、並べ替え、折りたたみの組み合わせを使用して、それぞれの更新を整理します。SQL Serverは、スプリットの結果をEager Table Spoolに保存し、一意のインデックスごとにそのセットを再生することで、非クラスター化インデックスに対してこのケースを最適化します(インデックスキーによる独自の並べ替え+アクションコード、および折りたたみ)。
統計更新への影響
自動統計更新(有効な場合)クエリオプティマイザは、既存の統計があることを統計情報や通知を必要とするときに発生する日付のうち(またはスキーマの変更により無効。記録された変更の数がしきい値を超えると、統計は古くなったと見なされます。
Split / Sort / Collapseの配置により、予想とは異なる行の変更が記録されます。これは、統計の更新が遅かれ早かれトリガーされる可能性があることを意味します。
上記の例では、キー列の行の変更は、4(影響を受けるテーブル行ごとに1つ)または5(折りたたみによって作成された削除/更新/挿入ごとに1つ)ではなく2(正味の変更)ずつ増加します。
さらに、元のクエリによって論理的に変更されなかった非キー列は、更新されたテーブル行の2倍(削除ごとに1回、挿入ごとに1回)の行修正を蓄積します。
記録される変更の数は、古いキー列の値と新しいキー列の値との重なりの程度(したがって、個別の削除と挿入を折りたたむことができる程度)に依存します。各実行間でテーブルをリセットすると、次のクエリは、重複が異なる行変更カウンタへの影響を示します。
UPDATE dbo.Banana SET pk = pk + 0; -- Full overlap
UPDATE dbo.Banana SET pk = pk + 1;
UPDATE dbo.Banana SET pk = pk + 2;
UPDATE dbo.Banana SET pk = pk + 3;
UPDATE dbo.Banana SET pk = pk + 4; -- No overlap