JOINクエリと複数クエリ


180

JOINクエリは、いくつかのクエリよりも高速ですか?(メインクエリを実行してから、メインクエリの結果に基づいて他の多くのSELECTを実行します)

それらを結合するとアプリケーションの設計が非常に複雑になるので、私は尋ねています

より速い場合、だれがどれだけ大まかに概算できますか?1.5倍の場合は気にしませんが、10倍の場合はそうだと思います。


私は彼らがより速くなると思います。1つのINSERTは、10の個別のINSERTクエリよりもはるかに高速であることを知っています。
アレックス、

1
複数のクエリがストアドプロシージャ内にあるかどうか、アプリケーションから発生したかどうかが重要になる場合があります(この情報を使用して質問を編集してください)。前者は後者よりもはるかに高速になります。
コロシアム2009

回答:


82

これはあいまいすぎて、特定のケースに関連する回答を提供できません。それは多くのものに依存します。Jeff Atwood(このサイトの創設者)が実際にこれについて書いた。ただし、ほとんどの場合、適切なインデックスがあり、JOINを適切に実行すると、通常、数回のトリップよりも1回のトリップの方が速くなります。


2
異なるキーで3つ以上のテーブルを結合する場合、多くの場合、データベース(mysqlなど)はテーブルごとに1つのインデックスしか使用できません。つまり、結合の1つは高速(そしてインデックスを使用)ですが、他の結合は非常に遅くなります。複数のクエリの場合、各クエリで使用するインデックスを最適化できます。
user151975

4
これは、「より高速」の定義に依存すると思います。たとえば、3 PK内部結合は、ネットワークオーバーヘッドのため、および各クエリを停止して準備し、送信する必要があるため、4回のラウンドトリップよりも速く回る場合があります。前のクエリが完了しました。ただし、負荷がかかっている状態でサーバーのベンチマークを行うと、ほとんどの場合、結合ではPKクエリよりもCPU時間がかかり、多くの場合、ネットワークオーバーヘッドも増加します。
mindplay.dk

97

内部結合の場合、一致する行のみを取得するため、単一のクエリは理にかなっています。左結合については、複数のクエリの方がはるかに優れています...私が行った次のベンチマークを見てください:

  1. 5つの結合を持つ単一のクエリ

    クエリ:8.074508秒

    結果サイズ:2268000

  2. 続けて5つのクエリ

    クエリの合計時間:0.00262秒

    結果のサイズ:165​​(6 + 50 + 7 + 12 + 90)

どちらの場合も同じ結果が得られることに注意してください(6 x 50 x 7 x 12 x 90 = 2268000)

左結合は、冗長なデータで指数関数的に多くのメモリを使用します。

2つのテーブルの結合のみを実行する場合、メモリ制限はそれほど悪くはないかもしれませんが、通常は3つ以上であり、異なるクエリの価値があります。

ちなみに、MySQLサーバーはアプリケーションサーバーのすぐ横にあるので、接続時間はごくわずかです。接続時間が秒単位の場合、おそらくメリットがあります

フランク


31
正しい心の誰も5つのテーブル間でクロス結合を行わないという迷惑な小さな事実を無視すると(そのため、ほとんどの場合それは意味をなさない)、「ベンチマーク」にはいくつかのメリットがあるかもしれません。しかし、左結合または内部結合は通常、キーによる検索であり(検索をはるかに高速にします)、データの複製は通常、実際よりもはるかに少なくなります。
cHao、2011年

12
@cHaoは誰が言うのですか?私はSMFとphpBBを調べて、3つのテーブルの間にJOINがあることを確認しました。プラグインや変更を追加すると、簡単に追加できます。どのような種類の大きなアプリケーションでも、多くのJOINが発生する可能性があります。おそらく、不適切に記述された、または誤用されたORMは、実際には必要ないテーブル(おそらくすべてのテーブル)を結合する可能性があります。
ナタリーアダムス

5
@NathanAdams:左結合と内部結合はまったく悪くありません。(実際、あちこちにテーブルを結合していないと、SQLが間違っています。)私が話していたのは、クロス結合です。これは、2つのテーブル間でさえ、ほとんどの場合望ましくないものです。さもなければ完全に偽の「2268000」という上記の結果を得る唯一の方法です。
cHao 2014年

2
しかし、結果を見てください。「結果サイズ:2268000」対「結果サイズ:165​​」。JOINのスローダウンは、レコードが互いに1対多の関係にあるためだと思います。一方、レコードが1対1の関係にある場合、JOINははるかに速くなり、結果は確実に得られません。 SELECTよりも大きいサイズ。
HoldOffHunger 2016年

3
@cHao明らかに、最初のコメントの時点ではMagentoに会っていません
vitoriodachef

26

この質問は古いですが、いくつかのベンチマークがありません。JOINを2つの競合他社に対してベンチマークしました。

  • N + 1クエリ
  • 2つのクエリ、2番目のクエリはWHERE IN(...)同等または同等のものを使用

結果は明らかです:MySQLの上で、JOINある非常に速いです。N + 1クエリは、アプリケーションのパフォーマンスを大幅に低下させる可能性があります。

JOIN対WHERE IN対N + 1

つまり、非常に少数の個別の外部レコードを指す多くのレコードを選択しない限りです。以下は、極端なケースのベンチマークです。

JOIN vs N + 1-同じ外部レコードを指すすべてのレコード

多対多のリレーションシップに参加している場合を除いて、これは一般的なアプリケーションで発生する可能性はほとんどありません。その場合、外部キーは他のテーブルにあり、メインテーブルのデータを何度も複製します。

取り除く:

  • *と1の関係の場合は、常に JOIN
  • *対多の関係の場合、2番目のクエリの方が高速かもしれませ

詳細については、Mediumに関する私の記事を参照しください。


22

実際に私は自分自身で答えを探してこの質問に行きました。与えられた答えを読んだ後、DBクエリのパフォーマンスを比較する最善の方法は、考慮すべき変数が多すぎるため、実際の数値を取得することであることにのみ同意できますしかし、それらの間の数を比較することは、ほとんどすべての場合に不利益につながると私は思います。つまり、数値は常に許容可能な数値と比較されるべきであり、明らかに互いに比較されるべきではないということです。

クエリの1つの方法に0.02秒かかる場合と、もう1つの方法に20秒かかる場合、それは大きな違いです。しかし、クエリの1つの方法に0.0000000002秒かかり、もう1つの方法に0.0000002秒かかる場合はどうでしょうか。どちらの場合も、一方の方法はもう一方の方法よりもなんと1000倍高速ですが、2番目の方法でも本当に「なんとなく」そうでしょうか。

個人的に見た結果の要点:パフォーマンスが良い場合は、簡単な解決策を検討してください。


4
もちろん、スケーリングを計画しているかどうかによって異なります。Facebookが始まったときのCuzには確かにそのようなクエリがあったはずですが、スケーリングを念頭に置いており、おそらくより複雑なソリューションではありますが、より効率的でした。
dudewad 2013

@dudewad理にかなっています。結局のところ、すべては必要なものに依存します。
Valentin Flachsel 2013

4
ハハええ...グーグルで失われた1ナノ秒は文字通り100億兆ドルに等しいので...しかしそれは単なる噂です。
dudewad 2013

2
@dudewad実際、Facebookが始まったとき、私は彼らがより簡単なソリューションを採用したことを保証します。ザッカーバーグ氏は、最初のバージョンをわずか2週間でプログラムしたと語った。新興企業は競争するために迅速に行動する必要があり、生き残る企業は通常、実際にそれが必要になるまでスケーリングについて心配する必要はありません。その後、何百万ドルもの投資額を得た後、彼らはリファクタリングし、パフォーマンスに特化したロックスタープログラマーを雇うことができます。あなたの意見では、Facebookは現在、わずかなパフォーマンス向上のために、より複雑なソリューションを採用することが多いと思いますが、ほとんどの人はFacebookをプログラミングしていません。
ダリン

15

50,000行のテーブルから1行を選択し、100,000行のテーブルから1行と結合する簡単なテストを行いました。基本的には次のように見えました:

$id = mt_rand(1, 50000);
$row = $db->fetchOne("SELECT * FROM table1 WHERE id = " . $id);
$row = $db->fetchOne("SELECT * FROM table2 WHERE other_id = " . $row['other_id']);

$id = mt_rand(1, 50000);
$db->fetchOne("SELECT table1.*, table2.*
    FROM table1
    LEFT JOIN table1.other_id = table2.other_id
    WHERE table1.id = " . $id);

2つの選択方法では、50,000回の読み取りに3.7秒かかりましたが、自宅の遅いコンピューターではJOINに2.0秒かかりました。INNER JOINとLEFT JOINは違いがありませんでした。複数の行をフェッチすると(たとえば、IN SETを使用して)、同様の結果が得られました。


1
通常のWebビューグリッドの場合と同様に行のページ(20または50など)を選択し、単一のLEFT JOINを2つのクエリと比較する場合、違いが逆になる可能性があります。 IN()を使用したSELECTクエリ。
JustAMartin 2017

列idとother_idはインデックス付けされていますか?
Aarish Ramesh

11

本当の質問は次のとおりです。これらのレコードには1対1の関係または1対多の関係がありますか?

TLDR回答:

1対1の場合は、JOINステートメントを使用します。

1対多の場合はSELECT、サーバー側のコード最適化で1つ(または複数)のステートメントを使用します。

SELECTを最適化に使用する理由と方法

SELECT1対多の関係に基づいてレコードの大規模なグループに対して(結合ではなく複数のクエリを使用して)実行JOINすると、指数関数的なメモリリークの問題があるため、最適な効率が得られます。すべてのデータを取得し、サーバー側のスクリプト言語を使用してデータを整理します。

SELECT * FROM Address WHERE Personid IN(1,2,3);

結果:

Address.id : 1            // First person and their address
Address.Personid : 1
Address.City : "Boston"

Address.id : 2            // First person's second address
Address.Personid : 1
Address.City : "New York"

Address.id : 3            // Second person's address
Address.Personid : 2
Address.City : "Barcelona"

ここでは、1つのselectステートメントですべてのレコードを取得しています。これはJOIN、これらのレコードの小さなグループを1つずつ、別のクエリのサブコンポーネントとして取得するよりも優れています。次に、次のようなサーバー側コードで解析します...

<?php
    foreach($addresses as $address) {
         $persons[$address['Personid']]->Address[] = $address;
    }
?>

最適化にJOINを使用しない場合

JOIN単一のレコードとの1対1の関係に基づいてレコードの大きなグループを作成するとSELECT、次のレコードタイプを取得するだけの複数のステートメントと比較して、最適な効率が得られます。

ただしJOIN、1対多の関係でレコードを取得する場合は非効率的です。

例:データベースBlogsには、Blogpost、Tag、およびCommentの3つの対象テーブルがあります。

SELECT * from BlogPost
LEFT JOIN Tag ON Tag.BlogPostid = BlogPost.id
LEFT JOIN Comment ON Comment.BlogPostid = BlogPost.id;

ブログ投稿が1つ、タグが2つ、コメントが2つある場合、次のような結果が得られます。

Row1: tag1, comment1,
Row2: tag1, comment2,
Row3: tag2, comment1,
Row4: tag2, comment2,

各レコードがどのように複製されているかに注意してください。わかりましたので、2つのコメントと2つのタグは4行です。4つのコメントと4つのタグがある場合はどうなりますか?あなたは8行を取得しません-あなたは16行を取得します:

Row1: tag1, comment1,
Row2: tag1, comment2,
Row3: tag1, comment3,
Row4: tag1, comment4,
Row5: tag2, comment1,
Row6: tag2, comment2,
Row7: tag2, comment3,
Row8: tag2, comment4,
Row9: tag3, comment1,
Row10: tag3, comment2,
Row11: tag3, comment3,
Row12: tag3, comment4,
Row13: tag4, comment1,
Row14: tag4, comment2,
Row15: tag4, comment3,
Row16: tag4, comment4,

テーブルやレコードなどを追加すると、問題はすぐに数百の行に広がり、ほとんどすべて冗長なデータでいっぱいになります。

これらの複製はあなたにどのような費用がかかりますか?メモリ(SQLサーバーと重複を削除しようとするコード内)およびネットワークリソース(SQLサーバーとコードサーバー間)。

ソース:https : //dev.mysql.com/doc/refman/8.0/en/nested-join-optimization.html ; https://dev.mysql.com/doc/workbench/en/wb-relationship-tools.html


あなたは要点を逃しています。1対(1対多)ではありません。行のセットがペアになっていることに意味があるかどうかについてです。接線方向に関連するデータのセットを2つだけ求めています。コメントを求めていた場合、たとえば、著者の連絡先情報を尋ねた場合、人々はおそらく複数のコメントを書くことができますが、それは参加としてより理にかなっています。
cHao

@cHao:コメントありがとうございます。上記の私の答えは、ここにあるMySQLドキュメントの概要です:dev.mysql.com/doc/workbench/en/wb-relationship-tools.html
HoldOffHunger

これはMySQLのドキュメントではありません。MySQLデータベースを操作するための特定のGUIツールのドキュメントです。また、結合が適切である(または適切でない)場合のガイダンスは提供されません。
cHao

@cHao:申し訳ありませんが、MySQL Server(TM)ではなく、MySQL WorkBench(TM)のMySQL(R)ドキュメントを意味しました。
HoldOffHunger 2018年

ペダントリーはさておき、関連性は明確ではありません。どちらも1対1および1対多の関係について述べていますが、ここで共通点が終わります。どちらにしても、問題はデータのセット間の関係についてです。関係のない2つのセットに参加すると、2つの組み合わせがすべて得られます。関連データを複数の選択に分割します。これで、疑わしい利点のために複数のクエリを実行し、MySQLの仕事を始めました。
cHao

8

個別のクエリと結合の両方を作成してから、それぞれの時間を計測します。実際の数値以外に役立つものはありません。

次に、さらに良い-各クエリの先頭に「EXPLAIN」を追加します。これにより、MySQLがデータのリクエストに応答するために使用しているサブクエリの数、および各クエリでスキャンされた行の数がわかります。


7

開発者の複雑さと比較してデータベースの複雑さに応じて、多くのSELECT呼び出しを実行する方が簡単な場合があります。

JOINと複数のSELECTSの両方に対していくつかのデータベース統計を実行してみてください。ご使用の環境で、JOINがSELECTよりも速いか遅いかを確認してください。

次に、それをJOINに変更すると、追加の日/週/月の開発作業が必要になる場合は、複数のSELECTを使用します

乾杯、

BLT


5

私の経験では、特に大規模なデータセットを取得する場合は、通常、複数のクエリを実行する方が高速であることがわかりました。

PHPなどの別のアプリケーションからデータベースを操作する場合、何回もサーバーにアクセスするという議論があります。

サーバーへのトリップの数を制限し、複数のクエリを実行する方法は他にもあります。多くの場合、クエリは高速であるだけでなく、アプリケーションを読みやすくすることもできます(例:mysqli_multi_query)。

私はSQLに関しては初心者ではありません。開発者、特に後輩はスマートに見えるため、非常に巧妙な結合を作成しようとすることに多くの時間を費やす傾向があると思いますが、実際に見えるデータを抽出するスマートな方法がありますシンプル。

最後の段落は個人的な意見でしたが、これが役に立てば幸いです。ベンチマークを行うべきだと言う人もいますが、私は他の人には同意します。どちらのアプローチも特効薬ではありません。


はい、クエリ自体だけでなく、アプリケーション内のデータ処理も考慮する必要があります。外部結合を使用してデータをフェッチする場合、アプリ(通常は一部のORMライブラリ)によって並べ替える必要がある冗長性(場合によっては非常に大きくなることがあります)があるため、要約すると、JOINクエリを使用した単一のSELECTがより多くのCPUを消費する可能性があります。 2つの単純なSELECTよりも時間が
かかる

4

結合を使用する必要があるかどうかは、何よりもまず、結合が理にかなっているかどうかについてです。他のほとんどすべてのケースでは、パフォーマンスが大幅に低下するため、その時点でのみ、考慮すべき何かであってもパフォーマンスがあります。

パフォーマンスの違いは、照会する情報の関連性に大きく関係しています。結合は機能し、データが関連付けられており、データに正しくインデックスを付けると結合は高速になりますが、結合はしばしば冗長性をもたらし、時には必要以上の結果をもたらします。また、データセットが直接関連していない場合、それらを1つのクエリに貼り付けると、デカルト積(基本的には行のすべての可能な組み合わせ)と呼ばれる結果が得られますが、これはほとんど望んでいることではありません。

これは多くの場合、多対1対多の関係によって引き起こされます。たとえば、HoldOffHungerの回答には、投稿、タグ、コメントに対する単一のクエリが含まれていました。タグと同様に、コメントは投稿に関連していますが、タグはコメントに関連していません。

+------------+     +---------+     +---------+
|  comment   |     |   post  |     |  tag    |
|------------|*   1|---------|1   *|---------|
| post_id    |-----| post_id |-----| post_id |
| comment_id |     | ...     |     | tag_id  |
| user_id    |     |         |     | ...     |
| ...        |     |         |     | ...     |
+------------+     +---------+     +---------+

この場合、少なくとも2つの個別のクエリであることが明白です。タグとコメントを結合しようとすると、2つの間に直接の関係がないため、タグとコメントの可能なすべての組み合わせになります。many * many == manymany。それとは別に、投稿とタグは無関係であるため、これら2つのクエリを並行して行うことができ、潜在的な利益につながります。

ただし、別のシナリオについて考えてみましょう。投稿にコメントを添付し、コメント投稿者の連絡先情報を取得します。

 +----------+     +------------+     +---------+
 |   user   |     |  comment   |     |   post  |
 |----------|1   *|------------|*   1|---------|
 | user_id  |-----| post_id    |-----| post_id |
 | username |     | user_id    |     | ...     |
 | ...      |     | ...        |     +---------+
 +----------+     +------------+

ここで結合を検討する必要があります。より自然なクエリであることに加えて、ほとんどのデータベースシステム(MySQLを含む)は、多くの賢い人々が多くのハードワークをそのようにクエリの最適化に費やしています。個別のクエリの場合、各クエリは前のクエリの結果に依存するため、クエリを並行して実行することはできず、合計時間はクエリの実際の実行時間だけでなく、結果のフェッチ、シフト、次のクエリのIDのためにそれらを介して、行を一緒にリンクするなど。


2番目のシナリオで多くのユーザー列を取得する(そして同じユーザーが複数回コメントする)場合でも、別のクエリで取得するのが最適かどうかという疑問が残ります。
Adrian Baker、

@AdrianBaker:私が言ったように、多くの賢い人々が多くのハードワークを投入します。SQLサーバーを最適化する場合、私の最初のアイデアは圧縮を使用することで、コードを変更せずに膨大な冗長性を排除します。まったく。次のレベルの最適化には、結果をテーブルに再構成し、それらを行IDのタプルと共に送信することが含まれます。クライアントライブラリは、必要に応じて、その側で簡単にアセンブルできます。
cHao

これらの最適化はどちらも、結合を使用して不思議に機能して冗長性を削減または排除することもできますが、関連するレコードをフェッチするために本質的にシリアルクエリを実行するのに役立つものはあまりありません。
cHao

3

スループットの点で速くなりますか?恐らく。ただし、同時に(データベースとスキーマに応じて)より多くのデータベースオブジェクトをロックする可能性があるため、同時実行性が低下します。私の経験では、実際にはデータベースが同じLAN上にあるほとんどのOLTPシステムで実際のボトルネックがネットワークになることはほとんどないのに、人々は「データベースラウンドトリップの減少」という議論に誤解されがちです。



1

バイナリの答えがないことを意味するいくつかの要因があります。パフォーマンスに最適なものは、環境によって異なります。ちなみに、識別子を使用した単一選択が1秒未満でない場合は、構成に問題がある可能性があります。

本当の質問は、データにどのようにアクセスしたいかです。単一選択は遅延バインディングをサポートします。たとえば、従業員情報のみが必要な場合は、従業員テーブルから選択できます。外部キーの関係を使用して、後で必要に応じて関連リソースを取得できます。選択にはすでにポイントするキーがあるので、選択は非常に高速になり、必要なものを取得するだけで済みます。ネットワーク遅延は常に考慮に入れられなければなりません。

結合は、すべてのデータを一度に取得します。レポートを生成したり、グリッドにデータを入力したりする場合は、これで十分です。コンパイルおよびオプトマイズされた結合は、このシナリオでは単一選択よりも高速になります。アドホック結合はそれほど高速ではない可能性があることに注意してください。それらを(ストアドプロシージャに)コンパイルする必要があります。速度の答えは実行計画によって異なります。実行計画では、DBMSがデータを取得するために実行する手順が正確に詳しく説明されています。


0

はい、JOINSを使用した1つのクエリの方が高速です。クエリを実行するテーブルの関係、データセットのサイズ、または主キーの場所がわからない場合、どれほど高速かを言うことはほとんど不可能です。

両方のシナリオをテストしてみませんか?

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.