次の表、一意のクラスター化インデックス、および統計を考えます。
CREATE TABLE dbo.Banana
(
pk integer NOT NULL,
c1 char(1) NOT NULL,
c2 char(1) NOT NULL
);
CREATE UNIQUE CLUSTERED INDEX pk ON dbo.Banana (pk);
CREATE STATISTICS c1 ON dbo.Banana (c1);
CREATE STATISTICS c2 ON dbo.Banana (c2);
INSERT dbo.Banana
(pk, c1, c2)
VALUES
(1, 'A', 'W'),
(2, 'B', 'X'),
(3, 'C', 'Y'),
(4, 'D', 'Z');
-- Populate statistics
UPDATE STATISTICS dbo.Banana;
統計行変更カウンターは、更新前に明らかに0を示しています。
-- Show statistics modification counters
SELECT
stats_name = S.[name],
DDSP.stats_id,
DDSP.[rows],
DDSP.modification_counter
FROM sys.stats AS S
CROSS APPLY sys.dm_db_stats_properties(S.object_id, S.stats_id) AS DDSP
WHERE
S.[object_id] = OBJECT_ID(N'dbo.Banana', N'U');
各pk
列の値を行ごとに1ずつ増分します。
-- Increment pk in every row
UPDATE dbo.Banana
SET pk += 1;
実行計画を使用します。
次の統計変更カウンターが生成されます。
pk
統計に2つの変更が表示されますが、c1
およびc2
ショー5?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の行が既に存在することです。SQLServerストレージエンジンでは、一意のインデックスを残す必要があります。単一のステートメント内でも、処理のすべての段階で一意です。これは、分割、並べ替え、折りたたみによって解決される問題です。
最初のステップは、各更新ステートメントを削除とその後の挿入に分割することです。
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はadjacentを組み合わせて削除し、同じキー値に対してを更新に挿入します。
-- 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に対する単一の削除とpk
に対する挿入が残っています。 = 5。
折りたたみ演算子は、キー列によって行をグループ化し、折りたたみ結果を反映するようにアクションコードを更新します。
この演算子は更新というラベルが付いていますが、挿入、更新、削除が可能です。行ごとのクラスター化インデックス更新によって実行されるアクションは、その行のアクションコードの値によって決まります。オペレーターには、この操作モードを反映するActionプロパティがあります。
上記の3つの更新は、維持されている一意のインデックスのキーを変更しないことに注意してください。実際、インデックスのkey列の更新をnon-key列(c1
およびc2
)の更新に変換しました。加えて、削除と挿入。削除も挿入も、偽の一意キー違反を引き起こすことはありません。
挿入または削除は、行のすべての列に影響を与えるため、すべての列に関連付けられている統計情報の変更カウンターが増加します。更新の場合、更新された列のいずれかが先行列である統計のみが、変更カウンターが増分されます(値が変更されていない場合でも)。
したがって、統計行変更カウンターは、pk
に2つの変更を示し、c1
とc2
に5つの変更を示します。
-- 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
注:baseオブジェクト(ヒープまたはクラスター化インデックス)に適用される変更のみ)統計行変更カウンターに影響します。非クラスター化インデックスは、基本オブジェクトに対して既に行われた変更を反映した二次構造です。統計行変更カウンターにはまったく影響しません。
オブジェクトに複数の一意のインデックスがある場合、個別の分割、並べ替え、折りたたみの組み合わせを使用して、それぞれの更新を整理します。 SQL Serverは、分割の結果をEager Table Spoolに保存し、一意のインデックスごとにそのセットを再生することによって、非クラスター化インデックスのこのケースを最適化します(独自のインデックスキーによる並べ替えキー+アクションコード、および折りたたみ)。
自動統計更新(有効になっている場合)は、クエリオプティマイザーが統計情報を必要とし、既存の統計が古くなっている(またはスキーマの変更により無効である)ことに気付いたときに発生します)。記録された変更の数がしきい値を超えると、統計は古くなったと見なされます。
分割/並べ替え/折りたたみの配置により、異なる行の変更が予想よりも記録されます。これは、統計更新が他の場合よりも遅かれ早かれトリガーされる可能性があることを意味します。
上記の例では、キー列の行変更は、4(影響を受けるテーブル行ごとに1つ)または5(Collapseによって生成される削除/更新/挿入ごとに1つ)ではなく、2(正味変更)ずつ増加します。
さらに、元のクエリによって論理的に変更されなかった非キー列は、double更新されたテーブル行(削除ごとに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