JOINを使用してテーブルを効率的に更新する


8

世帯の詳細が記載されたテーブルと、その世帯に関連するすべての人物の詳細が記載されたテーブルがあります。世帯テーブルには、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人の記録を持っています。現在、実行時間は非常に長くなっています。

さて、私の質問:

  1. このクエリをさらに最適化することは可能ですか?より一般的には、結合クエリを最適化するための経験則は何ですか?
  2. より良い実行時間で私が望む結果を達成できる別のクエリ構造はありますか?

私がしている実行計画アップロード SQLPerformance.comに全体のスクリプトは、SQL Server 2008によって生成されたが

回答:


19

私はテーブルの定義がこれに近いと確信しています:

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を有効にすると、次の計画が作成されます。

並列ネストループプラン

これで、並べ替えを回避し、結合に追加のメモリを必要とせず、並列処理を効果的に使用する計画ができました。このクエリは、他のクエリよりもはるかに優れたパフォーマンスを発揮するはずです。

私の記事「並列クエリ実行プランの強制」の並列処理に関する詳細:


1

クエリプランを見ると、実際の問題は結合自体ではなく、実際の更新プロセスである可能性があります。

私が見ることができるものから、あなたはあなたのデータベースのすべての個人レコードを更新し、インデックスを更新している可能性があります(これが他のどのインデックスを持っているのかわからないので、それが要因であるかどうかわかりません)

これが1回限りのタスクである場合、インデックスを無効にし、更新を実行してインデックスを再構築できますか?

データを入力したら、where句をクエリに追加して、更新が必要なレコードのみを更新できます。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.