大きなテーブルでの結合の最適化


10

2億5000万件のレコードを持つテーブルにアクセスしているクエリからさらにパフォーマンスを引き出そうとしています。実際の(推定ではない)実行プランを読んだところ、最初のボトルネックは次のようなクエリです。

select
    b.stuff,
    a.added,
    a.value
from
    dbo.hugetable a
    inner join
    #smalltable b on a.fk = b.pk
where
    a.added between @start and @end;

関連するテーブルとインデックスの定義については、下を参照してください。

実行計画は、ネストされたループが#smalltableで使用されていること、およびhugetableに対するインデックススキャンが480回(#smalltableの各行に対して)実行されていることを示しています。これは私には逆に思えるので、代わりにマージ結合を使用するように強制しようとしました:

select
    b.stuff,
    a.added,
    a.value
from
    dbo.hugetable a with(index = ix_hugetable)
    inner merge join
    #smalltable b with(index(1)) on a.fk = b.pk
where
    a.added between @start and @end;

問題のインデックス(完全な定義については以下を参照)は、列fk(結合述語)をカバーし、追加(where句で使用)およびid(役に立たない)を昇順で含み、valueを含みます

ただし、これを実行すると、クエリが2分半から9分以上に吹き飛ばされます。ヒントによって、各テーブルで1回のパスしか実行しないより効率的な結合が強制されることを期待していましたが、明らかにそうではありませんでした。

どんなガイダンスも大歓迎です。必要に応じて追加情報が提供されます。

アップデート(2011/06/02)

テーブルのインデックスを再編成したことで、大幅なパフォーマンスの向上が見られましたが、巨大なテーブルのデータを要約することに関して、新たな障害に直面しました。結果は月ごとの要約で、現在は次のようになります。

select
    b.stuff,
    datediff(month, 0, a.added),
    count(a.value),
    sum(case when a.value > 0 else 1 end) -- this triples the running time!
from
    dbo.hugetable a
    inner join
    #smalltable b on a.fk = b.pk
group by
    b.stuff,
    datediff(month, 0, a.added);

現在、hugetableにはクラスター化インデックスpk_hugetable (added, fk)(主キー)があり、非クラスター化インデックスはその逆ix_hugetable (fk, added)です。

上記の4列目がない場合、オプティマイザーは以前と同様にネストされたループ結合を使用し、#smalltableを外部入力として使用し、非クラスター化インデックスシークを内部ループとして使用します(再度480回実行)。気になるのは、推定された行(12,958.4)と実際の行(74,668,468)の違いです。これらのシークの相対コストは45%です。ただし、実行時間は1分未満です。

4列目では、実行時間は4分に急上昇します。今回(2実行)は同じ相対コスト(45%)でクラスター化インデックスを検索し、ハッシュ一致(30%)を介して集計し、#smalltable(0%)でハッシュ結合を実行します。

私の次の行動方針がわかりません。私の懸念は、日付範囲検索も結合述部も保証されていないこと、または結果セットを大幅に削減する可能性が高いことです。ほとんどの場合、日付範囲はレコードのおそらく10〜15%しかトリムせず、fkの内部結合はおそらく20〜30%を除外する可能性があります。


ウィルAの要求に応じて、次の結果sp_spaceused

name      | rows      | reserved    | data        | index_size  | unused
hugetable | 261774373 | 93552920 KB | 18373816 KB | 75167432 KB | 11672 KB

#smalltableは次のように定義されます:

create table #endpoints (
    pk uniqueidentifier primary key clustered,
    stuff varchar(6) null
);

一方、dbo.hugetableは次のように定義されています。

create table dbo.hugetable (
    id uniqueidentifier not null,
    fk uniqueidentifier not null,
    added datetime not null,
    value decimal(13, 3) not null,

    constraint pk_hugetable primary key clustered (
        fk asc,
        added asc,
        id asc
    )
    with (
        pad_index = off, statistics_norecompute = off,
        ignore_dup_key = off, allow_row_locks = on,
        allow_page_locks = on
    )
    on [primary]
)
on [primary];

次のインデックスが定義されています:

create nonclustered index ix_hugetable on dbo.hugetable (
    fk asc, added asc, id asc
) include(value) with (
    pad_index = off, statistics_norecompute = off,
    sort_in_tempdb = off, ignore_dup_key = off,
    drop_existing = off, online = off,
    allow_row_locks = on, allow_page_locks = on
)
on [primary];

IDのフィールドは、と主張した前回のDBAからアーチファクト冗長であるすべてのテーブルがどこにでもGUID、例外なくを持つ必要があります。


sp_spaceused 'dbo.hugetable'の結果を含めてもらえますか?
ウィルA

完了しました。テーブル定義の先頭のすぐ上に追加されました。
クイックジョースミス、

確かです。そのばかげたサイズが私がこれを調べている理由です。
クイックジョースミス、

回答:


5

ix_hugetable次の理由により、あなたはかなり役に立たないように見えます:

  • それクラスター化インデックス(PK)です。
  • クラスター化インデックスはすべての非キー列をINCLUDEするため、INCLUDEは違いを生じません(最も低いリーフの非キー値= INCLUDEd =クラスター化インデックスとは)

さらに:-追加またはfkを最初にする必要があります-IDを最初にする=あまり使用しない

クラスター化されたキーをに変更し(added, fk, id)て削除しix_hugetableます。あなたはすでに試しました(fk, added, id)。他に何もなければ、多くのディスク容量とインデックスのメンテナンスを節約できます

別のオプションは、テーブルの順序と方法を使用してJOIN / INDEXヒントを使用せずにFORCE ORDERヒントを試すことです。オプティマイザーのオプションを削除するため、個人的にJOIN / INDEXヒントを使用しないようにします。何年も前に、SQLグルを使用したセミナーで、巨大なテーブルに小さなテーブルを結合する場合にFORCE ORDERヒントを使用すると役立つと言われました。7年後のYMMV ...

ああ、そしてDBAがどこに住んでいるか教えてください。そうすればパーカッションの調整を手配できます。

6月2日更新後の編集

4番目の列は非クラスター化インデックスの一部ではないため、クラスター化インデックスを使用します。

NCインデックスを変更して値列を含めるようにして、クラスター化インデックスの値列にアクセスする必要がないようにします。

create nonclustered index ix_hugetable on dbo.hugetable (
    fk asc, added asc
) include(value)

注:値がnullにできない場合は、COUNT(*)意味的に同じです。しかし、SUMの場合は、存在ではなく実際の値が必要です。

例として、あなたが変更した場合COUNT(value)COUNT(DISTINCT value) せずに、それが値として値を処理する必要があるため、インデックスを変更することはない存在として、再びクエリを破る必要があります。

クエリには3つの列が必要です:追加、fk、値。最初の2つはフィルタリング/結合され、キー列も同様です。値は使用されるだけなので、含めることができます。カバリングインデックスの古典的な使用。


ああ、私は頭に、クラスター化されたインデックスと非クラスター化されたインデックスが異なる順序でfkと追加されていることを知っていました。そもそもこのようにセットアップされたなんて信じられないくらい、気付かなかったなんて信じられません。クラスター化されたインデックスを明日変更し、それが再構築されている間にコーヒーを飲みながら通りを下ります。
クイックジョースミス、

大きなテーブルでのシークの数を減らすためにFORCE ORDERでbashを実行しましたが、役に立ちませんでした。質問が更新されました。
クイックジョースミス

@Quick Joe Smith:私の回答を更新
gbn

はい、しばらくしてから試しました。インデックスの再構築には非常に時間がかかるため、私はそれを忘れてしまい、最初はまったく関係のないことを高速化すると思っていました。
クイックジョースミス

2

hugetableのみにインデックスを定義しaddedます。

DBは、マルチパート(マルチカラム)インデックスを使用します。これは、カラムリストの左から数えて、カラムリストの右端のみを使用します。fkクエリで最初のクエリのwhere句が指定されていないため、インデックスは無視されます。


実行プランは、インデックス(ix_hugetable)シークされていることを示しています。または、このインデックスはクエリに適切ではないと言っていますか?
クイックジョースミス、

インデックスは適切ではありません。それが「インデックスを使用する」方法を知っている人は誰でしょう。経験から、これはあなたの問題であることがわかります。それを試して、それがどうなるか教えてください。
ボヘミアン、

@Quick Joe Smith-@Bohemianの提案を試しましたか?結果はどこですか?
Lieven Keersmaekers、2011年

2
同意しません。ON句は最初に論理的に処理され、事実上WHEREであるため、OPは両方の列を最初に試す必要があります。fkのインデックス付けはまったくありません= JOINのfk値を取得するためのクラスター化インデックススキャンまたはキー検索。あなたが説明した振る舞いにいくつかの参照を追加できますか?特に SQL Serverの場合、このRDBMSについて回答した以前の履歴はほとんどありません。実際、-1はこのコメントを入力したことを振り返って
gbn

2

実行計画は、ネストされたループが#smalltableで使用されていること、およびhugetableに対するインデックススキャンが480回(#smalltableの各行に対して)実行されていることを示しています。

これは、ループジョインが正しい選択であると想定して、クエリオプティマイザーが使用すると予想される順序です。別の方法は、2億5000万回ループし、毎回#tempテーブルへのルックアップを実行することです。これには数時間/日かかる可能性があります。

MERGE結合で使用するように強制しているインデックスは、ほぼ2億5000万行*「各行のサイズ」-小さくはなく、少なくとも数GBです。sp_spaceused出力「数GB」から判断すると、かなり控えめな表現かもしれません。MERGE結合では、非常にI / Oが集中するインデックスをトロールする必要があります。


3つのタイプの結合アルゴリズムがあり、両方の入力が結合述語で順序付けられている場合、マージ結合が最高のパフォーマンスを発揮すると私は理解しています。正しいか間違っているか、これは私が得ようとしている結果です。
クイックジョースミス

2
しかし、これ以外にもあります。#smalltableに多数の行がある場合、マージ結合が適切な場合があります。その名前が示すように、行数が少ない場合は、ループ結合が適切な選択肢になる可能性があります。#smalltableに1つまたは2つの行があり、他のテーブルの一部の行と一致した場合を想像してください。ここでマージ結合を正当化するのは困難です。
ウィルA

私にはそれ以上のものがあると思った。それが何であるか私は知りませんでした。おそらくすでにご想像のとおり、データベースの最適化は私の強力なスーツとは言えません。
クイックジョースミス、

@Quick Joe Smith-sp_spaceusedをありがとう。75GBのインデックスと18GBのデータ-ix_hugetableはテーブルの唯一のインデックスではありませんか?
ウィルA

1
+1意志。プランナーは現在正しいことをしています。テーブルがクラスター化されているため、問題はランダムディスクシークにあります。
Denis de Bernardy、2011年

1

インデックスが正しくありません。インデックスのdosとdontsを参照してください。

現状では、有用な唯一のインデックスは、小さなテーブルの主キーのインデックスです。したがって、唯一の合理的な計画は、小さなテーブルをseqスキャンして、混乱を巨大なテーブルと入れ子にしてループすることです。

にクラスタ化インデックスを追加してみてくださいhugetable(added, fk)。これにより、プランナーは巨大なテーブルから該当する行を探し出し、ネストしたループまたは小さなテーブルにマージ結合する必要があります。


そのリンクをありがとう。明日仕事に着いたらやってみます。
Quick Joe Smith、
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.