3

質問が既に回答されている場合は、最初に申し訳ありません。ここと Google の両方を検索しましたが、回答が見つかりませんでした。この質問はおそらく尋ねられていませんが、「LEFT JOINを使用するだけ」および「配列に保存する」というすべての回答の下にかなり深く隠されています。

複数のテーブルにまたがる大量のデータをロードする必要があります (それを別のデータベース エンジンに挿入しますが、それは重要ではありません。SELECT を最適化する必要があります)。

テーブル レイアウトは次のようになります。

a_id フィールドを持つテーブル A a_id と b_id フィールドを持つテーブル B b_id と c_id フィールドを持つテーブル C ... (このようにさらに 3 ~ 4 レベル進みます)。

現在、この方法でデータにアクセスしています(疑似コード):

query1 = SELECT ... FROM TableA WHERE something=$something

foreach query1 as result1:

    query2 = SELECT ... FROM TableB WHERE b_id=result1.a_id

    foreach query2 as result2:
        query3 = SELECT ... FROM TableC WHERE bc_id=result2.b_id

            foreach query3 as result3:
                // Another few levels of this, see the millions of SELECTs coming?

これまでに見つけた唯一の解決策は次のとおりです。

  1. 遅い方法を使用して複数のクエリを送信します (現在のソリューションであり、小さなテスト セットを完了するには時間がかかります)
  2. 大量の LEFT JOIN を使用して、すべてのデータを 1 つのクエリにまとめます。大量のデータを何千回も送信する必要があるため、各行には親テーブルのコンテンツが含まれるため、クライアント側でこれを適切なテーブルに再度分割するための凝ったロジックが必要になります。(私はOOPを使用し、各テーブルはオブジェクトにマップされ、各オブジェクトには相互が含まれています)。
  3. テーブル A の各オブジェクトを配列に格納し、すべてのテーブル B を読み込み、配列に格納し、テーブル C に進みます。小さなセットで機能しますが、私のものは数 GB の大きさで、RAM にまったく収まりません。

このようなループで 1 秒あたり 10k のクエリを実行しないようにする方法はありますか?

(私は PHP を使用しており、MySQL から MongoDB に変換しています。MongoDB は、このようにネストされたオブジェクトをより適切に処理します。これが役立つ場合)

編集:私がやろうとしていることとその理由について、いくつかの混乱があるようです。もっとよく説明しようとします。新しい構造へのバッチ変換を行う必要があります。新しい構造は非常にうまく機能します。気にする必要はありません。私は非常に古い Web サイトをゼロから作り直しています。ストレージ エンジンとして MongoDB を選択しました。これは、このようなネストされたデータが大量にあるためです。MongoDB は私にとって非常にうまく機能します。MySQL に戻すことは、私にとって選択肢でさえありません。新しい構造とコードはすでに十分に確立されており、私はこれに約 1 年間取り組んできました。私は現在のスキーマを最適化する方法を探していません。できません。データはそのようになっており、データベース全体を読み取る必要があります。一度。それから私はそれで終わりです。

私がする必要があるのは、古い Web サイトからデータをインポートし、これを処理して変換し、新しい Web サイトに挿入できるようにすることだけです。これが MySQL です。以前のサイトはごく普通の PHP/MySQL サイトでした。たくさんのテーブルがあります (実際には約 70 かそこら)。多くのユーザーはいませんが、各ユーザーは 7 つのテーブルにまたがる大量のデータを持っています。

私が現在行っていることは、各ユーザー (1 クエリ) をループすることです。これらのユーザー (70k) ごとに、ユーザーごとに 10 ~ 80 行を含むテーブル A を読み込みます。次に、A のループごとにテーブル B をクエリします (つまり、70k の 10-80 倍)。これには、各 A に対して 1 ~ 16 行が含まれます。次に、各 B に対して 1 ~ 4 行を保持するテーブル C があります。 *80*70k クエリを実行する必要があります。次に、D、Cごと​​に1〜32行、Eごとに1〜16行、Fごとに1〜16行、テーブルFには数百万行があります。

問題

  • 最終的に、本番データベースがローカル マシン上になく、5 ~ 10 ミリ秒離れた MySQL サーバーに対して、数百万とは言わないまでも数千のクエリを実行することになります。0.01 ミリ秒でも、ネットワーク遅延だけで何時間もかかります。ローカル レプリカを作成したので、制限付きのテスト セットはかなり高速に実行されますが、このような数 GB のデータをダウンロードするにはまだ時間がかかります。

  • メンバー テーブルを RAM に保持し、場合によってはテーブル A に保持することもできます。これにより、何千ものクエリを実行する代わりに、各データベースを 1 回でダウンロードできます。 PHP(少なくともコマンドライン)を使用します。これは、RAMを厳密に制御できるC++プログラムの場合よりも少し多くのメモリを使用します。したがって、このソリューションも機能しません。

  • すべてのテーブルを一緒に JOIN することもできますが、2 ~ 3 個のテーブルで機能する場合、これを 7 個のテーブルで行うと、サーバーから同じデータを何百万回も使用せずに転送するため、帯域幅がさらに大幅に失われます (コードを作成している間も)。それらを適切な順序で分割するのは非常に複雑です)。

質問: データベースに頻繁にクエリを実行しない方法はありますか? たとえば、これらすべてのデータセットをこの順序で必要とするプロシージャまたは何かを使用して MySQL サーバーに伝えると、各行でクエリをやり直す必要がなくなり、データベースは継続的にデータを吐き出します。現在の問題は、スクリプトとデータベースの両方が常に別のクエリを待機しているため、多くのクエリを実行していることです。クエリ自体は、実際には非常に高速で、インデックス付きの int フィールドに対する基本的な準備済み SELECT クエリです。

これは、私が過去に MySQL で常に陥っていた問題であり、今まで実際に問題を引き起こしたことはありませんでした。現在の状態では、スクリプトが完了するまでに数日ではないにしても数時間かかります。悪くはないのですが、何か良い方法があれば教えていただきたいです。そうでない場合は、終了するまで待ちます。少なくとも最大 3 ~ 4 回実行します (2 ~ 3 回のテストを実行し、データが正しく変換されていることをユーザーに確認してもらい、バグを修正し、再試行し、最後のバグ修正を含む最終実行)。

前もって感謝します!

4

2 に答える 2

0

7 つのテーブルが ID でリンクされていると仮定して、次のようにします。

最初のクエリ

'SELECT * FROM table_a WHERE a_id IN (12,233,4545,67676,898999)'
// store the result in $result_of_first_query

次に foreach を実行し、次のクエリで使用する ID をコンマ区切り変数 (csv) で選択します。

foreach($result_of_first_query as $a_row_from_first_table)
{
    $csv_for_second_query = $csv_for_second_query.$a_row_from_first_table['b_id'].",";
}

$csv_for_second_query = trim($csv_for_second_query,", "); // problem is we will have a lot of duplicate entries
$temp_arr = array(); // so lets remove the duplicates
$temp_arr = explode(",",$csv_for_second_query);  // explode values in array
$temp_arr = array_unique($temp_arr);  // remove duplicates
$csv_for_second_query = implode(",",$temp_arr);  // create csv string again. ready!

2番目のテーブルでは、JOINに必要なすべての値を1つのクエリだけで取得します(mysqlではなく、phpでこれを行います)

2 番目のクエリ

'SELECT * FROM table_b where a_id IN ('.$csv_for_second_query.')'
// store the result in $result_of_second_query;

次に、2 つの配列をプログラムで結合するだけです。

$result_a_and_b = array(); // we will store the joined result of every row here

// lets scan every row from first table
foreach($result_of_first_query as $inc=> $a_row_from_first_table)
{
    // assign every row from frist table to result_a_and_b 
    $result_a_and_b[$inc]['a']=$a_row_from_first_table;

    $inc_b=0; // counter for the joins that will happen by data from second table

    // for every row from first table we will scan every row from second table
    // so we need this nested foreach
    foreach($result_of_second_query as $a_row_from_second_table)
    {
        // are data need to join? if yes then do so! :)
        if($a_row_from_first_table['a_id']==$a_row_from_second_table['a_id'])
        {
            $result_a_and_b[$inc]['b'][$inc_b]=$a_row_from_second_table; // "join" in our "own" way :)
            ++$inc_b; // needed for the next join
        }
    }
}

これで、次の形式の配列 $result_a_and_b ができました。

$result_a_and_b[INDEX]['a']
$result_a_and_b[INDEX]['b'][INDEX]

したがって、2 つのクエリを使用すると、TABLE_A_ROWS_NUMBER + 1 のような結果が得られます (1 つは最初のテーブルの最初のクエリです)。

このように、必要な数のレベルを続けてください。

  1. テーブルをリンクする ID でデータベースをクエリします
  2. CSV 文字列で ID を取得する
  3. WHERE id IN(11,22,33,44,55,.....) を使用して次のクエリを実行します。
  4. プログラムで参加する

ヒント:unset()一時変数のメモリを解放するために使用できます。

「データベースに頻繁にクエリを実行しない方法はありますか?」という質問に答えたと思います。

注: コードはタイプミスについてテストされていません。コンマを 1 つまたは 2 つ見逃した可能性があります。

私はあなたがポイントを得ることができると信じています:)それが役に立てば幸いです!

于 2013-06-18T08:34:34.500 に答える