5

私はこの2つのmysqlテーブルを持っています:TableAとTableB

TableA
* ColumnAId
* ColumnA1
* ColumnA2
TableB
* ColumnBId
* ColumnAId
* ColumnB1
* ColumnB2

PHPでは、この多次元配列形式が欲しかった

$array = array(
    array(
        'ColumnAId' => value,
        'ColumnA1' => value,
        'ColumnA2' => value,
        'TableB' => array(
            array(
                'ColumnBId' => value,
                'ColumnAId' => value,
                'ColumnB1' => value,
                'ColumnB2' => value
            )
        )
    )
);

このようにループできるように

foreach($array as $i => $TableA) {
    echo 'ColumnAId' . $TableA['ColumnAId'];
    echo 'ColumnA1' . $TableA['ColumnA1'];
    echo 'ColumnA2' . $TableA['ColumnA2'];
    echo 'TableB\'s';
    foreach($value['TableB'] as $j => $TableB) {
        echo $TableB['...']...
        echo $TableB['...']...
    }
}

私の問題は、この目標を達成できるようにMySQLデータベースにクエリを実行する最良の方法または適切な方法は何ですか?

Solution1 --- 私が使っているもの

$array = array();
$rs = mysqli_query("SELECT * FROM TableA", $con);
while ($row = mysqli_fetch_assoc($rs)) {
    $rs2 = mysqli_query("SELECT * FROM Table2 WHERE ColumnAId=" . $row['ColumnAId'], $con);
    // $array = result in array
    $row['TableB'] = $array2;
}

私のコードが常にデータベースを照会しているのではないかと疑っています。

ソリューション2

$rs = mysqli_query("SELECT * FROM TableA JOIN TableB ON TableA.ColumnAId=TableB.ColumnAId");
while ($row = mysqli_fet...) {
    // Code
}

2 番目のソリューションは 1 回だけクエリを実行しますが、TableB.ColumnAId (1 TableA.ColumnAId = 1000 TableB.ColumnAId) ごとに TableA に数千行、TableB に数千行ある場合、このソリューション 2 はソリューション 1 よりも時間がかかりますか?

4

4 に答える 4

6

提案された 2 つの解決策はおそらく最適ではありませんが、解決策 1 は予測不可能であり、したがって本質的に欠陥があります。

大規模なデータベースを扱うときに最初に学ぶことの 1 つは、クエリを実行するための「最善の方法」は、データベース内の要因 (メタデータと呼ばれる) に依存することが多いということです。

  • 何行ありますか。
  • クエリしているテーブルの数。
  • 各行のサイズ。

このため、問題に対する特効薬の解決策はありそうにありません。あなたのデータベースは私のデータベースと同じではありません。利用可能な最高のパフォーマンスが必要な場合は、さまざまな最適化をベンチマークする必要があります。

データベースに正しいインデックスを適用して構築する(そして MySQL でのインデックスのネイティブ実装を理解する) と、より多くのことができることがわかるでしょう。

めったに破ってはならないクエリに関するゴールデン ルールがいくつかあります。

  • ループ構造でそれらを行わないでください。しばしば魅力的ですが、接続の作成、クエリの実行、および応答の取得のオーバーヘッドは高くなります。
  • SELECT *必要でない限り避けてください。より多くの列を選択すると、SQL 操作のオーバーヘッドが大幅に増加します。
  • あなたのインデックスを知っています。この機能を使用して、EXPLAIN使用されているインデックスを確認し、使用可能なものを使用してクエリを最適化し、新しいインデックスを作成します。

このため、2 つのうち 2 番目のクエリ (SELECT *必要な列のみに置き換える) を使用しますが、最適化する時間があれば、クエリを構造化するためのより良い方法がおそらくあります

ただし、速度だけを考慮してはいけません。提案 1 を使用しない大きな理由があります。

予測可能性: 読み取りロックが優れている理由

他の回答の1つは、テーブルを長期間ロックすることは悪いことであり、したがって複数クエリのソリューションは良いことを示唆しています。

これは真実からかけ離れたものではないと私は主張します。SELECT実際、多くの場合、単一のロッククエリを実行することの予測可能性は、最適化と速度の利点よりも、そのクエリを実行するためのより大きな議論であると私は主張します。

まずSELECT、MyISAM または InnoDB データベース (MySQL のデフォルト システム) で (読み取り専用) クエリを実行すると、テーブルが読み取りロックされます。SELECTこれにより、読み取りロックが解除される (クエリが完了するか失敗する)まで、テーブルで書き込み操作が行われなくなります。他のSELECTクエリは影響を受けないため、マルチスレッド アプリケーションを実行している場合は引き続き機能します。

この遅延は良いことです。なぜ、あなたは尋ねるかもしれませんか?リレーショナル データの整合性。

例を見てみましょう: ゲームで多数のユーザーのインベントリに現在あるアイテムのリストを取得する操作を実行しているので、次の結合を行います。

SELECT * FROM `users` JOIN `items` ON `users`.`id`=`items`.`inventory_id` WHERE `users`.`logged_in` = 1;

このクエリ操作中に、ユーザーがアイテムを別のユーザーにトレードするとどうなりますか? このクエリを使用すると、クエリを開始したときのゲームの状態が表示されます。アイテムは、クエリを実行する前にそれを持っていたユーザーのインベントリに一度存在します。

しかし、ループで実行するとどうなるでしょうか?

ユーザーが詳細を読み取る前または後に取引したかどうか、および 2 人のプレーヤーのインベントリを読み取る順序に応じて、次の 4 つの可能性があります。

  1. アイテムは、最初のユーザーのインベントリに表示される可能性があります (ユーザー B をスキャン -> ユーザー A をスキャン -> 取引されたアイテム、またはユーザー B をスキャン -> ユーザー A をスキャン -> 取引されたアイテム)。
  2. アイテムは 2 番目のユーザーのインベントリに表示される可能性があります (アイテムの取引 -> ユーザー A のスキャン -> ユーザー B のスキャン OR アイテムの取引 -> ユーザー B のスキャン -> ユーザー A のスキャン)。
  3. アイテムは両方のインベントリに表示される可能性があります (スキャン ユーザー A -> 取引されたアイテム -> スキャン ユーザー B)。
  4. アイテムは、ユーザーのインベントリのどちらにも表示されませんでした (ユーザー B をスキャン -> 取引されたアイテム -> ユーザー A をスキャン)。

これが意味することは、クエリの結果を予測したり、リレーショナルの整合性を確保したりすることができないということです。

火曜日の真夜中にアイテム ID が 1000000 の男に 5,000 ドルを寄付する予定なら、手元に 10,000 ドルあることを願っています。スナップショットの取得時に一意の項目が一意であることにプログラムが依存している場合、この種のクエリで例外が発生する可能性があります。

ロックは、予測可能性を高め、結果の整合性を保護するため、優れています。

注: transactionでループを強制的にロックすることもできますが、それでも遅くなります。

ああ、そして最後に、プリペアドステートメントを使用してください!

次のようなステートメントは絶対に使用しないでください。

mysqli_query("SELECT * FROM Table2 WHERE ColumnAId=" . $row['ColumnAId'], $con);

mysqli準備済みステートメントをサポートしています。それらについて読んで使用すると、データベースに何かひどいことが起こるのを避けるのに役立ちます。

于 2013-08-16T13:24:11.577 に答える
2

間違いなく2番目の方法。ネストされたクエリは、ネストされたクエリごとにすべてのクエリ オーバーヘッド (実行、ネットワークなど) を毎回取得するため、醜いものです。一方、単一のJOINクエリは 1 回実行されます。

単純なルールは、クエリをサイクルで使用しないことです-一般に。1 つのクエリが複雑すぎる場合は例外があり、パフォーマンスのために分割する必要がありますが、特定のケースでは、ベンチマークとメジャーによってのみ表示できます。

于 2013-08-16T13:13:04.573 に答える
2

アプリケーション コードでデータのアルゴリズム評価を行いたい場合 (これは正しいことだと思います)、SQL をまったく使用しないでください。SQL は、リレーショナル データベースから計算で得られたデータを要求する人間が読める方法になるように作られています。

このような場合、キー値ストアのような別のストレージ/取得の可能性を使用することを好む必要があります(そこには永続的なストアがあり、MySQL の新しいバージョンは InnoDB に対してもキー値インターフェイスを公開していますが、それでもリレーショナルを使用しています)キー値ストレージ用のデータベース、別名ジョブの不適切なツール)。

それでもソリューションを使用したい場合:

基準。

複数のクエリを発行すると、単一のクエリよりも高速になることがよくあります。これは、MySQL が解析するクエリが少なく、オプティマイザーが行う作業が少なく、多くの場合、MySQL オプティマイザーが失敗するだけだからです (これが、STRAIGHT などの理由です)。 JOIN およびインデックス ヒントが存在します)。また、失敗しない場合でも、基盤となるストレージ エンジンや、一度にデータにアクセスしようとするスレッドの数によっては、複数のクエリの方が高速になる可能性があります (ロックの粒度 -これは、更新クエリが混在している場合にのみ適用されます- MyISAM も MyISAM もInnoDB は、デフォルトで SELECT クエリのテーブル全体をロックします)。繰り返しますが、トランザクションを使用しない場合、2 つのソリューションで異なる結果が得られる可能性もあります。複数のクエリを使用する場合と単一のクエリを使用する場合では、クエリ間でデータが変わる可能性があるからです。

一言で言えば、あなたの質問には、あなたが投稿/要求したものよりも多くの方法があり、一般的な回答が提供できるものがあります。

あなたのソリューションについて:a)データの変更が一般的である、および/またはb)テーブルにアクセスして更新する同時実行スレッド(リクエスト)が多数ある環境がある場合は、最初のソリューションをお勧めします(分割するとロックの粒度が向上します)クエリ、およびクエリのキャッシュ可能性) ; データベースが別のネットワーク上にある場合、たとえばネットワーク遅延が問題である場合は、おそらく最初の解決策の方が適切です (ただし、私が知っているほとんどの人は、ソケット接続を使用して同じサーバーに MySQL を使用しており、通常はローカル ソケット接続を使用していません)。待ち時間が長い)。

実際に for ループを実行する頻度によっても状況が変わる場合があります。

再び:ベンチマーク


考慮すべきもう 1 つの点は、メモリ効率とアルゴリズム効率です。後者はどちらの場合も O(n) 程度ですが、結合に使用するデータの種類によっては、2 つのいずれかで悪化する可能性があります。たとえば、文字列を使用して結合する場合 (実際にはそうすべきではありませんが、言いません)、より PHP に依存するソリューションでのパフォーマンスは、PHP ハッシュ マップ アルゴリズム (PHP の配列は事実上ハッシュ マップです) と、ただし、mysql 文字列インデックスは通常固定長であるため、データによっては適用できない場合があります。

メモリ効率については、どちらのソリューションでもphp配列を使用しているため(メモリの点で非常に非効率的です!)、マルチクエリバージョンの方が確かに優れていますが、結合はいくつかの状況に応じて一時テーブルを使用する場合があります(通常はそうすべきです) 't、しかしそうする場合があります-クエリに EXPLAIN を使用して確認できます)

于 2013-08-16T13:48:14.333 に答える