29

それぞれ約 1 億レコードの 2 つの大規模なテーブルがあり、2 つの間で内部結合を実行する必要がありました。さて、どちらのテーブルも非常に単純です。説明は次のとおりです。

BioEntity テーブル:

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

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

  • EMGId (整数)
  • ID (整数)
  • 名前 (nvarchar 4000、これはやり過ぎです)
  • TypeId (整数)
  • LastModified (日付)

BioEntityId を EGM テーブルにある PId に関連付けるために、一致する名前を取得する必要があります。もともと、私は単一の内部結合ですべてを実行しようとしましたが、クエリに時間がかかりすぎているように見え、データベースのログファイル (単純な回復モード) が使用可能なすべてのディスク領域 (200 GB をわずかに超える) をなんとか使い果たしました。データベースが 18 GB を占有する場合)、クエリは 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 日間実行され、いずれも終了しませんでした。そのため、データベース チューニング アドバイザを実行して動作を確認してみました。古いインデックスを削除し、代わりに統計と2つのクラスター化されたインデックスを作成することを提案しました(各テーブルに1つずつ、奇妙に感じるTypeIdを含むだけです-または単にばかげています-しかし、とにかく試してみました)。

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

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

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

4

16 に答える 16

18

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

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

于 2009-11-17T16:26:55.093 に答える
11

巨大な結合の場合、明示的に選択すると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)
于 2009-11-17T16:25:40.807 に答える
8

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

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

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

于 2009-11-18T04:32:09.100 に答える
6

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

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

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

于 2009-11-17T22:20:07.277 に答える
4

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

于 2009-11-17T16:26:50.210 に答える
3

推奨されるように、結合をより合理的にするために名前をハッシュします。可能であれば、ルックアップによるバッチのインポート中に 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
于 2009-11-17T16:44:03.833 に答える
2

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

于 2009-11-17T16:54:04.360 に答える
1

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

于 2009-11-17T21:39:13.000 に答える
1

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

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

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

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

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

于 2009-11-17T21:46:01.120 に答える
1

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

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

于 2009-11-17T16:26:07.690 に答える
1

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

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

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

于 2009-11-17T22:31:32.980 に答える
1

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

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

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

私の2セント

于 2009-11-19T03:46:37.643 に答える
1

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

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

多くの要因に応じて、まだ長く実行されます (特に、新しいテーブルにどれだけのデータをコピーしているのか?) が、これには数日もかからないはずです。

于 2009-11-17T22:04:13.017 に答える
0

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

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

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

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

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

于 2009-11-18T10:51:16.773 に答える
0

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

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

本当に名前で結合する必要がある場合は、名前を ID に変換するヘルパー テーブルを検討して、基本的に非正規化された設計を一時的に修復します (完全に修復できない場合)。

チェックサムについてのアイデアも良いかもしれませんが、私自身はあまり遊んだことがありません。

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

于 2009-11-19T02:50:40.123 に答える
0

DB に高度なリレーショナル操作を要求するわけではないので、これを簡単にスクリプト化できます。大規模でありながら単純なクエリで DB を強制終了する代わりに、2 つのテーブルをエクスポートしてみてください (バックアップからオフライン コピーを取得できますか?)。

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

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

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

于 2009-11-17T17:08:09.223 に答える