web-dev-qa-db-ja.com

SQL:2つの巨大なテーブルを結合する内部

それぞれ約1億レコードの巨大なテーブルが2つあり、2つの間で内部結合を実行する必要があるのではないかと心配しています。現在、両方のテーブルは非常に単純です。説明は次のとおりです。

BioEntityテーブル:

  • BioEntityId(int)
  • 名前(nvarchar 4000、これはやり過ぎですが)
  • TypeId(int)

EGMテーブル(実際には、一括インポート操作の結果である補助テーブル):

  • EMGId(int)
  • PId(int)
  • 名前(nvarchar 4000、これはやり過ぎですが)
  • TypeId(int)
  • LastModified(日付)

BioEntityIdをEGMテーブルにあるPIdに関連付けるには、一致する名前を取得する必要があります。元々、1つの内部結合ですべてを実行しようとしましたが、クエリに時間がかかりすぎて、データベースのログファイル(単純リカバリモード)が使用可能なすべてのディスク領域(200 GBをわずかに超える)を処理することができました。データベースが18GBを占有し、2日間待った後、クエリが失敗する場合、私が間違っていなければ。ログが大きくならないように管理しましたが(現在33 MBのみ)、クエリは6日間ノンストップで実行されており、すぐに停止するようには見えません。

私はそれをかなりまともなコンピューター(4GB RAM、Core 2 Duo(E8400)3GHz、Windows Server 2008、SQL Server 2008)で実行していますが、コンピューターが30秒ごとに(ギブまたはテイク)ジャムすることがあります。数秒。そのため、他の用途に使用するのは非常に難しく、本当に神経質になっています。

さて、これがクエリです:

 SELECT EGM.Name, BioEntity.BioEntityId INTO AUX
 FROM EGM INNER JOIN BioEntity 
 ON EGM.name LIKE BioEntity.Name AND EGM.TypeId = BioEntity.TypeId

いくつかのインデックスを手動で設定しました。 EGMとBioEntityの両方に、TypeIdとNameを含む非クラスター化カバーインデックスがありました。ただし、クエリは5日間実行され、それも終了しませんでしたので、Database TuningAdvisorを実行して動作させてみました。古いインデックスを削除し、代わりに統計と2つのクラスター化インデックスを作成することを提案しました(各テーブルに1つ、かなり奇妙だと思うTypeIdが含まれているだけです-または単にばかげています-とにかくやってみました)。

それは今6日間実行されています、そして私はまだ何をすべきかわかりません...何かアイデアの人?どうすればこれをより速く(または少なくとも有限に)することができますか?

更新:-わかりました。クエリをキャンセルし、サーバーを再起動してOSを再起動して実行しました。提案された変更を使用してワークフローを再実行します。具体的には、nvarcharフィールドを大幅にトリミングします。サイズを小さくし、「like」を「= "」に置き換えます。これには少なくとも2時間かかるので、後でさらに更新を投稿します

更新2(1PM GMT時間、2009年11月18日):-推定実行プランでは、テーブルスキャンに関する67%のコストと、それに続く33%のハッシュ一致が明らかになっています。次は、0%の並列処理(これは奇妙ではありませんか?これは、推定実行プランを使用するのは初めてですが、この特定の事実が眉をひそめただけです)、0%のハッシュ一致、0%の並列処理、0%の上位、0 %テーブルを挿入し、最後に別の0%を選択します。予想通り、インデックスはがらくたのようです。手動のインデックスを作成し、くだらない提案されたインデックスを破棄します。

23
João Pereira

巨大な結合の場合、loop joinを明示的に選択すると処理が高速化されることがあります。

SELECT EGM.Name, BioEntity.BioEntityId INTO AUX
FROM EGM 
INNER LOOP JOIN BioEntity 
    ON EGM.name LIKE BioEntity.Name AND EGM.TypeId = BioEntity.TypeId

いつものように、あなたの推定実行計画を投稿することは、私たちがより良い答えを提供するのを助けるかもしれません。

編集:両方の入力がソートされている場合(カバーするインデックスを使用してソートする必要があります)、 MERGE JOIN を試すことができます:

SELECT EGM.Name, BioEntity.BioEntityId INTO AUX
FROM EGM 
INNER JOIN BioEntity 
    ON EGM.name LIKE BioEntity.Name AND EGM.TypeId = BioEntity.TypeId
OPTION (MERGE JOIN)
7
Andomar

私はSQLチューニングの専門家ではありませんが、VARCHARフィールドで数億行を結合することは、私が知っているどのデータベースシステムでも良い考えとは思えません。

エンジンが実際のVARCHARデータを確認する前に、各テーブルに整数列を追加し、NAMEフィールドでハッシュを計算して、妥当な数に一致する可能性のあるものを取得してみてください。

17
Larry Lustig

まず、1億行の結合は、まったく不合理または珍しいことではありません。

ただし、表示されているパフォーマンスの低下の原因は、INTO句に関連している可能性があります。これにより、結合を行うだけでなく、結果を新しいテーブルに書き込むことにもなります。 ログファイルが非常に大きくなることについてのあなたの観察は、基本的にこれの確認です。

試してみるべきことの1つは、INTOを削除して、そのパフォーマンスを確認することです。パフォーマンスが妥当な場合は、書き込みが遅いことに対処するために、DBログファイルがデータとは別の物理ボリュームにあることを確認する必要があります。そうでない場合、ディスクヘッドはデータの読み取りとログの書き込みを行うときにスラッシュ(多くのシーク)を行い、パフォーマンスは低下します(おそらく、そうでない場合の1/40から1/60になります)。 )。

7
RickNZ

少し話題から外れているかもしれませんが、「コンピューターが30秒ごとに(ギブまたはテイク)数秒間ジャムすることがあることに気づきました。」

この動作は、ギガバイトの情報をコピーしている間(およびクエリはほとんどデータをコピーしている間)、安価なRAID5アレイ(または単一ディスクの場合)に特徴的です。

問題の詳細-クエリをより小さなブロックに分割できませんか? A、Bなどで始まる名前や特定の範囲のIDのように?これにより、トランザクション/ロックのオーバーヘッドを大幅に減らすことができます。

6
Arvo

'LIKE'演算子を削除してみます。ワイルドカードマッチングを行っていないようです。

4
Jim B

推奨されるように、結合をより合理的にするために名前をハッシュします。可能であれば、ルックアップを介してバッチのインポート中にIDを割り当てることを検討することを強く検討します。これにより、後で結合を実行する必要がなくなります(また、このような非効率的な結合を繰り返し実行する必要が生じる可能性があります)。

TypeIDにこのインデックスがあるようです。これが選択的である場合、これは非常に役立ちます。さらに、名前のハッシュを含む列を同じインデックスに追加します。

SELECT EGM.Name
       ,BioEntity.BioEntityId
INTO AUX 
FROM EGM 
INNER JOIN BioEntity  
    ON EGM.TypeId = BioEntity.TypeId -- Hopefully a good index
    AND EGM.NameHash = BioEntity.NameHash -- Should be a very selective index now
    AND EGM.name LIKE BioEntity.Name
3
Cade Roux

私が提供する可能性のあるもう1つの提案は、クエリを調整するために1億行すべてを一度に処理するのではなく、データのサブセットを取得することです。これにより、クエリがいつ終了するかを確認するのにそれほど多くの時間を費やす必要がなくなります。次に、クエリ実行プランを調べることを検討できます。これにより、目前の問題に対する洞察も得られる可能性があります。

2
Wil P

私は箱の外で問題を解決しようとします、多分データベースよりはるかに良くそしてより速く仕事をすることができる他のいくつかのアルゴリズムがあるでしょう。もちろん、それはすべてデータの性質に依存しますが、かなり高速な文字列検索アルゴリズム(Boyer-Moore、ZBoxなど)やその他のデータマイニングアルゴリズム(MapReduce?)があります。データエクスポートを慎重に作成することで、次のことが可能になります。問題を曲げて、よりエレガントで高速なソリューションに適合させます。また、問題をより適切に並列化することも可能であり、単純なクライアントで周囲のシステムのアイドルサイクルを利用することで、これを支援できるフレームワークがあります。

これの出力は、データベースから完全なデータをはるかに高速にフェッチするために使用できるrefidタプルのリストである可能性があります。

これはインデックスの実験を妨げるものではありませんが、結果を6日間待たなければならない場合は、他の可能なオプションの探索にリソースを費やすことが正当化されると思います。

私の2セント

1
Newtopian

いくつかのインデックスを手動で設定しました。 EGMとBioEntityの両方に、TypeIdとNameを含む非クラスター化カバーインデックスがありました。ただし、クエリは5日間実行され、終了しなかったため、Database TuningAdvisorを実行して動作させてみました。古いインデックスを削除し、代わりに統計と2つのクラスター化インデックスを作成することを提案しました(各テーブルに1つ、かなり奇妙だと思うTypeIdが含まれているだけです-または単にばかげています-とにかくやってみました)。

両方のテーブルでTypeIdにクラスター化インデックスを作成したとのことですが、各テーブルにはすでに主キーがあります(それぞれ、BioEntityIdとEGMId)。 TypeIdをこれらのテーブルのクラスター化インデックスにしたくない。 BioEntityIdとEGMIdをクラスター化する必要があります(これにより、物理的にディスク上のクラスター化インデックスの順にデータが並べ替えられます。ルックアップに使用する外部キーの非クラスター化インデックス。つまり、TypeId。主キーをクラスター化して、両方のテーブルに非クラスター化インデックスを追加してみてください。 TypeId。

私たちの環境では、1つあたり約1,000万から2,000万レコードのテーブルがあります。 1つまたは2つの列で2つのデータセットを結合する、あなたと同様の多くのクエリを実行します。 each外部キーのインデックスを追加すると、パフォーマンスが大幅に向上します。

1億レコードの場合、これらのインデックスには大量のディスク領域が必要になることに注意してください。ただし、ここではパフォーマンスが重要であるように思われるため、それだけの価値があるはずです。

K.スコットはかなり良い記事を持っています ここ それはいくつかの問題をより深く説明しています。

1
karlgrz

なぜnvarchar?ベストプラクティスは、Unicodeサポートが必要ない(または必要になると予想される)場合は、varcharを使用することです。最長の名前が200文字未満だと思われる場合は、その列をvarchar(255)にします。あなたに推奨されたハッシュがコストがかかるシナリオを見ることができます(このデータベースは挿入集約的であるようです)。ただし、それだけのサイズと、名前の頻度とランダムな性質により、ハッシュ(ハッシュに依存)または名前でインデックスを作成するほとんどのシナリオで、インデックスはすぐに断片化されます。

上記のように名前列を変更し、クラスター化インデックスTypeId、EGMId/BioentityId(いずれかのテーブルの代理キー)を作成します。そうすれば、TypeIdでうまく結合でき、Nameでの「大まかな」結合のループが少なくなります。このクエリが実行される時間を確認するには、TypeIdの非常に小さなサブセットに対してクエリを試してください。これにより、実行時間の見積もりが得られます(ただし、キャッシュサイズ、メモリサイズ、ハードディスク転送速度などの要素は無視される場合があります)。

編集:これが進行中のプロセスである場合は、将来のインポート/ダンプのために、2つのテーブル間に外部キー制約を適用する必要があります。進行中でない場合は、ハッシュがおそらく最善の方法です。

1
marr75

1億レコードは巨大です。専用のテストサーバーが必要になるほど大きなデータベースで作業することをお勧めします。同じマシンを使用して、そのようなクエリを実行しながら他の作業を行うことは実用的ではありません。

ハードウェアはかなり機能的ですが、それだけ大きな結合を適切に実行するには、さらに多くの電力が必要になります。 8GBのクアッドコアシステムが良いスタートです。それを超えて、インデックスが正しく設定されていることを確認する必要があります。

1
Dave Swersky

ここでいくつかの以前の投稿を繰り返します(私は投票します)...

TypeIdはどの程度選択的ですか? 1億以上の行に5、10、または100の個別の値しかない場合、特にすべての行を選択しているため、インデックスは何の役にも立ちません。

両方のテーブルのCHECKSUM(Name)に列を作成することをお勧めします。おそらく、これを永続化された計算列にします。

CREATE TABLE BioEntity
 (
   BioEntityId  int
  ,Name         nvarchar(4000)
  ,TypeId       int
  ,NameLookup  AS checksum(Name) persisted
 )

次に、そのようなインデックスを作成します(クラスター化を使用しますが、クラスター化されていない場合でも役立ちます)。

CREATE clustered INDEX IX_BioEntity__Lookup on BioEntity (NameLookup, TypeId)

(BOLを確認してください。環境に適用される可能性のある、計算列のインデックスの作成には規則と制限があります。)

両方のテーブルで実行すると、次のように修正された場合にクエリをサポートするための非常に選択的なインデックスが提供されます。

SELECT EGM.Name, BioEntity.BioEntityId INTO AUX
 FROM EGM INNER JOIN BioEntity 
 ON EGM.NameLookup = BioEntity.NameLookup
  and EGM.name = BioEntity.Name
  and EGM.TypeId = BioEntity.TypeId

多くの要因にもよりますが、それでも長く実行されます(特に、新しいテーブルにコピーするデータの量が原因ですか?)が、これには数日もかかりません。

1
Philip Kelley

主キーまたはインデックスはありますか?段階的に選択できますか?つまり、「A%」のような名前、「B%」のような名前などです。

1
DForck42

実行時間は結合によるものなのか、データ転送によるものなのか。

Name列の平均データサイズが150文字であると仮定すると、実際には300バイトとレコードごとの他の列があります。これに1億レコードを掛けると、約30GBのデータをクライアントに転送できます。クライアントをリモートで実行しますか、それともサーバー自体で実行しますか?たぶんあなたは30GBのデータがあなたのクライアントに転送されるのを待つでしょう...

EDIT:わかりました、Auxテーブルに挿入しているようです。データベースのリカバリモデルの設定は何ですか?

ハードウェア側のボトルネックを調査するために、制限リソースがデータの読み取りであるかデータの書き込みであるかが興味深い場合があります。たとえば、Windowsパフォーマンスモニターの実行を開始し、ディスクの読み取りと書き込みのためにキューの長さをキャプチャできます。

理想的には、速度を上げるために、dbログファイル、入力テーブル、および出力テーブルを別々の物理ボリュームに配置する必要があります。

0
Jan

DBに派手な関係演算を実行するように要求していないので、これを簡単にスクリプト化できます。大規模で単純なクエリでDBを強制終了する代わりに、2つのテーブルをエクスポートしてみてください(バックアップからオフラインコピーを取得できますか?)。

テーブルをエクスポートしたら、この単純な結合を実行するスクリプトを記述します。実行にはほぼ同じ時間がかかりますが、DBを強制終了することはありません。

データのサイズとクエリの実行にかかる時間の長さのため、これを頻繁に行うことはないため、オフラインバッチプロセスは理にかなっています。

スクリプトの場合、大きなデータセットにインデックスを付けてから、小さなデータセットを反復処理して、大きなデータセットのインデックスを検索します。実行するのはO(n * m)になります。

0
jpeacock

ハッシュ一致が消費するリソースが多すぎる場合は、たとえば一度に10000行のバッチでクエリを実行し、TypeID列を「ウォーク」します。 TypeIDの選択性については言わなかったが、おそらくこれほど小さなバッチを実行し、一度に1つ以上のTypeIDを完全にカバーできるほど十分に選択的である。バッチでループ結合も探しているので、それでもハッシュ結合が得られる場合は、ループ結合を強制するか、バッチサイズを小さくしてください。

バッチを使用すると、単純なリカバリモードでも、トランザクションログが非常に大きくなるのを防ぐことができます。単純なリカバリモードでも、トランザクション全体を開いたままにする必要があるため、実行しているような大規模な結合は大量のスペースを消費しますが、バッチを実行すると、バッチごとにログファイルを再利用でき、サイズを必要な最大値に制限します。 1つのバッチ操作。

本当にNameに参加する必要がある場合は、名前をIDに変換するヘルパーテーブルを検討してください。基本的に、非正規化されたデザインを一時的に修復します(永続的に修復できない場合)。

チェックサムについてのアイデアも良いかもしれませんが、私自身はそれほど遊んでいません。

いずれにせよ、そのような巨大なハッシュ一致は、バッチループ結合ほど実行されません。マージ結合を取得できれば、それは素晴らしいことです...

0
ErikE