私はテーブルの定義がこれに近いと確信しています:
CREATE TABLE dbo.households
(
tempId integer NOT NULL,
n integer NOT NULL,
HHID integer IDENTITY NOT NULL,
CONSTRAINT [UQ dbo.households HHID]
UNIQUE NONCLUSTERED (HHID),
CONSTRAINT [PK dbo.households tempId, n]
PRIMARY KEY CLUSTERED (tempId, n)
);
CREATE TABLE dbo.persons
(
tempId integer NOT NULL,
sporder integer NOT NULL,
n integer NOT NULL,
PERID integer IDENTITY NOT NULL,
HHID integer NOT NULL,
CONSTRAINT [UQ dbo.persons HHID]
UNIQUE NONCLUSTERED (PERID),
CONSTRAINT [PK dbo.persons tempId, n, sporder]
PRIMARY KEY CLUSTERED (tempId, n, sporder)
);
これらのテーブルまたはデータの統計情報はありませんが、次のようにすると、少なくともテーブルのカーディナリティが正しく設定されます(ページ数は推測です)。
UPDATE STATISTICS dbo.persons
WITH
ROWCOUNT = 5239842,
PAGECOUNT = 100000;
UPDATE STATISTICS dbo.households
WITH
ROWCOUNT = 1928783,
PAGECOUNT = 25000;
クエリプラン分析
あなたが今持っているクエリは:
UPDATE P
SET HHID = H.HHID
FROM dbo.households AS H
JOIN dbo.persons AS P
ON P.tempId = H.tempId
AND P.n = H.n;
これはかなり非効率的な計画を生成します:
この計画の主な問題は、ハッシュ結合とソートです。どちらもメモリの許可が必要です(ハッシュ結合はハッシュテーブルを作成する必要があり、ソートはソートの進行中に行を格納するためのスペースが必要です)。Plan Explorerは、このクエリに765 MBが付与されたことを示しています。
これは、1つのクエリ専用に使用するかなりのサーバーメモリです。さらに、このメモリの付与は、行数とサイズの見積もりに基づいて実行が開始される前に修正されます。
実行時にメモリが不足していることが判明した場合、ハッシュまたはソート、あるいはその両方の少なくとも一部のデータが物理tempdbディスクに書き込まれます。これは「流出」と呼ばれ、処理が非常に遅くなる可能性があります。これらの流出を(SQL Server 2008で)プロファイラーイベントのハッシュ警告とソート警告を使用して追跡できます。
ハッシュテーブルのビルド入力の見積もりは非常に優れています。
ソート入力の推定値は、それほど正確ではありません。
チェックにはプロファイラーを使用する必要がありますが、この場合、ソートがtempdbに流出するのではないかと思います。ハッシュテーブルが流出する可能性もありますが、それほど明確ではありません。
このクエリ用に予約されたメモリは、同時に実行されるため、ハッシュテーブルとソートの間で分割されることに注意してください。Memory Fractionsプランプロパティは、各操作で使用されることが予想されるメモリ許可の相対的な量を示します。
なぜソートしてハッシュするのですか?
クエリオプティマイザーによって並べ替えが導入され、クラスター化キーの順序で行がクラスター化インデックス更新演算子に到達するようになっています。これにより、テーブルへの順次アクセスが促進されます。多くの場合、ランダムアクセスよりもはるかに効率的です。
ハッシュ結合は、入力が(とにかく最初の概算で)同様のサイズであるため、それほど明白ではありません。ハッシュ結合は、1つの入力(ハッシュテーブルを作成する入力)が比較的小さい場合に最適です。
この場合、オプティマイザの原価計算モデルは、ハッシュ結合が3つのオプション(ハッシュ、マージ、ネストされたループ)の中で安価であると判断します。
パフォーマンスの向上
コストモデルは常に正しいとは限りません。特にスレッド数が増えると、並列マージ結合のコストを過大評価する傾向があります。クエリヒントを使用してマージ結合を強制できます。
UPDATE P
SET HHID = H.HHID
FROM dbo.households AS H
JOIN dbo.persons AS P
ON P.tempId = H.tempId
AND P.n = H.n
OPTION (MERGE JOIN);
これにより、それほど多くのメモリを必要としないプランが生成されます(マージ結合にはハッシュテーブルが必要ないため)。
マージ結合はその結合キーの順序(tempId、n)しか保持しないが、クラスター化されたキーは(tempId、n、sporder)であるため、問題のあるソートはまだ残っています。マージ結合プランは、ハッシュ結合プランと同じように機能します。
ネストされたループ結合
ネストされたループ結合を試すこともできます:
UPDATE P
SET HHID = H.HHID
FROM dbo.households AS H
JOIN dbo.persons AS P
ON P.tempId = H.tempId
AND P.n = H.n
OPTION (LOOP JOIN);
このクエリの計画は次のとおりです。
このクエリプランは、オプティマイザーのコストモデルでは最悪と見なされますが、非常に望ましい機能がいくつかあります。第1に、ネストされたループ結合は、メモリの付与を必要としません。次に、Persons
明示的なソートが不要になるように、テーブルからのキーの順序を保持できます。この計画は比較的うまく機能し、おそらく十分に優れているかもしれません。
ネストされた並列ループ
ネストされたループ計画の大きな欠点は、単一のスレッドで実行されることです。このクエリは並列処理の恩恵を受ける可能性がありますが、オプティマイザはここでそれを行うことに利点がないと判断します。これも必ずしも正しいとは限りません。残念ながら、並列プランを取得するための組み込みのクエリヒントはありませんが、文書化されていない方法があります。
UPDATE t1
SET t1.HHID = t2.HHID
FROM dbo.persons AS t1
INNER JOIN dbo.households AS t2
ON t1.tempId = t2.tempId AND t1.n = t2.n
OPTION (LOOP JOIN, QUERYTRACEON 8649);
QUERYTRACEON
ヒントでトレースフラグ8649を有効にすると、次の計画が作成されます。
これで、並べ替えを回避し、結合に追加のメモリを必要とせず、並列処理を効果的に使用する計画ができました。このクエリは、他のクエリよりもはるかに優れたパフォーマンスを発揮するはずです。
私の記事「並列クエリ実行プランの強制」の並列処理に関する詳細: