[パート1|パート2|パート3|パート4]
このシリーズの最初のパートでは、ハロウィーンの問題がUPDATE
にどのように適用されるかを見ました。 クエリ。簡単に要約すると、問題は、更新するレコードの検索に使用されるインデックスのキーが更新操作自体によって変更されることでした(キーを拡張するのではなく、インデックスに含まれる列を使用するもう1つの理由)。クエリオプティマイザは、問題を回避するために実行プランの読み取り側と書き込み側を分離するためにEagerTableSpool演算子を導入しました。この投稿では、同じ根本的な問題がINSERT
にどのように影響するかを確認します。 およびDELETE
ステートメント。
ステートメントを挿入
ハロウィーン保護が必要な条件について少し知ったので、INSERT
を作成するのは非常に簡単です。 同じインデックス構造のキーからの読み取りとキーへの書き込みを含む例。最も簡単な例は、テーブル内の行を複製することです(新しい行を追加すると、クラスター化インデックスのキーが必然的に変更されます):
CREATE TABLE dbo.Demo ( SomeKey integer NOT NULL, CONSTRAINT PK_Demo PRIMARY KEY (SomeKey) ); INSERT dbo.Demo SELECT SomeKey FROM dbo.Demo;
問題は、実行プランの読み取り側が新しく挿入された行に遭遇する可能性があり、その結果、行が永久に(または少なくとも何らかのリソース制限に達するまで)追加されるループが発生する可能性があることです。クエリオプティマイザはこのリスクを認識し、必要な相分離を提供するためにEagerTableSpoolを追加します :
より現実的な例
テーブル内のすべての行を複製するクエリを作成することはおそらくありませんが、INSERT
のターゲットテーブルでクエリを作成する可能性があります。 SELECT
のどこかに表示されます 句。 1つの例は、宛先にまだ存在しないステージングテーブルからの行を追加することです。
CREATE TABLE dbo.Staging ( SomeKey integer NOT NULL ); -- Sample data INSERT dbo.Staging (SomeKey) VALUES (1234), (1234); -- Test query INSERT dbo.Demo SELECT s.SomeKey FROM dbo.Staging AS s WHERE NOT EXISTS ( SELECT 1 FROM dbo.Demo AS d WHERE d.SomeKey = s.SomeKey );をテストします。
実行計画は次のとおりです。
この場合の問題は微妙に異なりますが、それでも同じコア問題の例です。ターゲットのデモテーブルには値「1234」はありませんが、ステージングテーブルにはそのようなエントリが2つ含まれています。相分離がない場合、最初に検出された「1234」の値は正常に挿入されますが、2番目のチェックでは、値「1234」が存在することがわかり、再度挿入を試みません。ステートメントは全体として正常に完了します。
これは、この特定のケースで望ましい結果を生み出す可能性があります(そして、直感的に正しいように見えるかもしれません)が、正しい実装ではありません。 SQL標準では、データ変更クエリを、制約の読み取り、書き込み、チェックの3つのフェーズが完全に別々に発生するかのように実行する必要があります(パート1を参照)。
単一の操作として挿入するすべての行を検索する場合、この値はまだターゲットに存在しないため、ステージングテーブルから両方の「1234」行を選択する必要があります。したがって、実行プランは両方を挿入しようとする必要があります。 ステージングテーブルの「1234」行。主キー違反が発生しました:
メッセージ2627、レベル14、状態1、1行目PRIMARYKEY制約「PK_Demo」の違反。
オブジェクト「dbo.Demo」に重複キーを挿入できません。
重複キー値は( 1234)。
ステートメントは終了しました。
テーブルスプールによって提供される相分離により、ターゲットテーブルに変更が加えられる前に、存在のすべてのチェックが確実に完了します。上記のサンプルデータを使用してSQLServerでクエリを実行すると、(正しい)エラーメッセージが表示されます。
ターゲットテーブルがSELECT句でも参照されているINSERTステートメントには、ハロウィーン保護が必要です。
ステートメントの削除
ハロウィーンの問題はDELETE
には当てはまらないと思われるかもしれません 行を複数回削除しようとしても、実際には問題にならないためです。ステージングテーブルの例を変更して、削除することができます。 ステージングに存在しないデモテーブルの行:
TRUNCATE TABLE dbo.Demo; TRUNCATE TABLE dbo.Staging; INSERT dbo.Demo (SomeKey) VALUES (1234); DELETE dbo.Demo WHERE NOT EXISTS ( SELECT 1 FROM dbo.Staging AS s WHERE s.SomeKey = dbo.Demo.SomeKey );
実行プランにテーブルスプールがないため、このテストは直感を検証しているようです。
このタイプのDELETE
各行には一意の識別子(テーブルがヒープの場合はRID、クラスター化インデックスキーの場合はRID、それ以外の場合は一意化子)があるため、相分離は必要ありません。このユニークな行ロケーターは安定したキーです –この計画の実行中に変更できるメカニズムがないため、ハロウィーンの問題は発生しません。
ハロウィーン保護を削除
それでも、DELETE
が発生するケースは少なくとも1つあります。 ハロウィーンの保護が必要です:プランが削除されている行以外のテーブルの行を参照している場合。これには、階層関係がモデル化されるときに一般的に見られる自己結合が必要です。簡単な例を以下に示します。
CREATE TABLE dbo.Test ( pk char(1) NOT NULL, ref char(1) NULL, CONSTRAINT PK_Test PRIMARY KEY (pk) ); INSERT dbo.Test (pk, ref) VALUES ('B', 'A'), ('C', 'B'), ('D', 'C');
ここで定義されているのは実際には同じテーブルの外部キー参照であるはずですが、設計が一時的に失敗することは無視しましょう。それでも構造とデータは有効です(そして、現実の世界で外部キーが省略されているのは悲しいことに非常に一般的です)。とにかく、当面のタスクは、 refがある行を削除することです。 列が存在しないpkを指している 価値。自然なDELETE
この要件に一致するクエリは次のとおりです。
DELETE dbo.Test WHERE NOT EXISTS ( SELECT 1 FROM dbo.Test AS t2 WHERE t2.pk = dbo.Test.ref );
クエリプランは次のとおりです。
このプランには、高価なEagerTableSpoolが含まれていることに注意してください。ここでは相分離が必要です。そうしないと、結果が行の処理順序に依存する可能性があるためです。
実行エンジンがpkの行から開始する場合 =B、一致する行が見つかりません( ref =Aであり、 pkの行はありません =A)。実行すると、 pkの行に移動します =C、 ref が指す行Bを削除したため、これも削除されます。 桁。最終的に、この順序で反復処理を行うと、テーブルからすべての行が削除されますが、これは明らかに正しくありません。
一方、実行エンジンが pkで行を処理した場合 =D最初に、一致する行が見つかります( ref =C)。実行が逆方向に継続されたと仮定するpk 順序で、テーブルから削除される行は、 pkの行のみになります。 =B.これは正しい結果です(読み取り、書き込み、検証の各フェーズが重複することなく順番に実行されたかのようにクエリを実行する必要があることを忘れないでください)。
制約検証のための相分離
余談ですが、前の例に同じテーブルの外部キー制約を追加すると、相分離の別の例を見ることができます。
DROP TABLE dbo.Test; CREATE TABLE dbo.Test ( pk char(1) NOT NULL, ref char(1) NULL, CONSTRAINT PK_Test PRIMARY KEY (pk), CONSTRAINT FK_ref_pk FOREIGN KEY (ref) REFERENCES dbo.Test (pk) ); INSERT dbo.Test (pk, ref) VALUES ('B', NULL), ('C', 'B'), ('D', 'C');
INSERTの実行プランは次のとおりです。
プランは同じテーブルから読み取らないため、挿入自体はハロウィーン保護を必要としません(データソースは、Constant Scanオペレーターによって表されるメモリ内の仮想テーブルです)。ただし、SQL標準では、書き込みフェーズの完了後にフェーズ3(制約チェック)を実行する必要があります。このため、相分離の熱心なテーブルスプールが後の計画に追加されます。 Clustered Index Indexであり、各行がチェックされて外部キー制約が有効なままであることを確認する直前。
セットベースの宣言型SQL変更クエリを堅牢な反復物理実行プランに変換するのは難しいビジネスだと考え始めている場合は、更新処理(Halloween Protectionはごく一部です)がなぜであるかがわかり始めています。クエリプロセッサの最も複雑な部分。
DELETEステートメントには、ターゲットテーブルの自己結合が存在するHalloweenProtectionが必要です。
概要
ハロウィーン保護は、データを変更する実行プランでは高価な(ただし必要な)機能になる可能性があります(「変更」には、行を追加、変更、または削除するすべてのSQL構文が含まれます)。 UPDATE
にはハロウィーン保護が必要です INSERT
の場合、共通のインデックス構造のキーが読み取られ、変更される計画 プランの読み取り側でターゲットテーブルが参照されているプラン、およびDELETE
のプラン ターゲットテーブルでの自己結合が実行される計画。
このシリーズの次のパートでは、MERGE
にのみ適用される特別なハロウィーン問題の最適化について説明します。 ステートメント。
[パート1|パート2|パート3|パート4]