世帯の詳細が記載されたテーブルと、世帯に関連するすべての人物の詳細が記載されたテーブルがあります。世帯テーブルには、2つの列を使用して定義された主キーがあります-[tempId,n]
。 personテーブルには、3つの列を使用して定義された主キーがあります[tempId,n,sporder]
主キーのクラスター化インデックスによって指定された並べ替えを使用して、各世帯[HHID]
および各個人[PERID]
レコードに一意のIDを生成しました(以下のスニペットはPERIDを生成するためのものです):
ALTER TABLE dbo.persons
ADD PERID INT IDENTITY
CONSTRAINT [UQ dbo.persons HHID] UNIQUE;
今、私の次のステップは、各人を対応する世帯に関連付けることです。 [PERID]
を[HHID]
にマップします。 2つのテーブル間の横断歩道は、2つの列に基づいています[tempId,n]
。このために、次の内部結合ステートメントがあります。
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;
私は合計で1928783世帯の記録と5239842人の記録を持っています。現在、実行時間は非常に長くなっています。
今、私の質問:
SQLPerformance.comへのスクリプト全体に対して、SQL Server 2008によって生成された 実行プランをアップロード を生成しました
私はテーブルの定義がこれに近いと確信しています:
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;
これはかなり非効率的な計画を生成します:
この計画の主な問題は、ハッシュ結合とソートです。どちらもメモリの許可が必要です(ハッシュ結合はハッシュテーブルを作成する必要があり、ソートはソートの進行中に行を格納するためのスペースが必要です)。プランエクスプローラーは、このクエリが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);
このクエリの計画は次のとおりです。
このクエリプランは、オプティマイザーのコストモデルでは最悪と見なされますが、非常に望ましい機能がいくつかあります。まず、ネストされたループ結合は、メモリの付与を必要としません。次に、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を有効にすると、次の計画が作成されます。
これで、並べ替えを回避し、結合に追加のメモリを必要とせず、並列処理を効果的に使用する計画ができました。このクエリは、他のクエリよりもはるかに優れたパフォーマンスを発揮するはずです。
私の記事の並列処理に関する詳細 並列クエリ実行プランの強制 :
クエリプランを見ると、実際の問題は結合自体ではなく、実際の更新プロセスである可能性があります。
私が見ることができるものから、あなたはあなたのデータベースのすべての個人レコードを更新し、インデックスを更新している可能性があります(これが他のどのインデックスを持っているのかわからないので、それが要因であるかどうかわかりません)
これが1回限りのタスクである場合、インデックスを無効にし、更新を実行してインデックスを再構築できますか?
データを入力したら、where句をクエリに追加して、更新が必要なレコードのみを更新できます。