JOIN requêtes vs requêtes multiples


180

Les requêtes JOIN sont-elles plus rapides que plusieurs requêtes? (Vous exécutez votre requête principale, puis vous exécutez de nombreux autres SELECT en fonction des résultats de votre requête principale)

Je demande car les REJOINDRE compliquerait BEAUCOUP la conception de mon application

S'ils sont plus rapides, quelqu'un peut-il approximer de combien? Si c'est 1,5x, je m'en fiche, mais si c'est 10x, je suppose que oui.


Je suppose qu'ils seraient plus rapides. Je sais qu'un INSERT par rapport à 10 requêtes INSERT individuelles est beaucoup plus rapide.
alex

1
Il peut être important que vos requêtes multiples se trouvent dans une procédure stockée ou si elles proviennent de l'application (modifiez votre question avec cette information). Le premier sera beaucoup plus rapide que le dernier.
colithium

Réponses:


82

C'est beaucoup trop vague pour vous donner une réponse pertinente à votre cas spécifique. Cela dépend de beaucoup de choses. Jeff Atwood (fondateur de ce site) a en fait écrit à ce sujet . Pour la plupart, cependant, si vous avez les bons index et que vous faites correctement vos JOINs, il sera généralement plus rapide de faire 1 voyage que plusieurs.


2
si vous joignez 3 tables ou plus sur des clés différentes, souvent les bases de données (ie mysql) ne peuvent utiliser qu'un seul index par table, ce qui signifie peut-être que l'une des jointures sera rapide (et utilisera un index) alors que les autres seront extrêmement lentes. Pour plusieurs requêtes, vous pouvez optimiser les index à utiliser pour chaque requête.
user151975

4
Je pense que cela dépend de votre définition de «plus rapide» ... par exemple, 3 jointures internes PK peuvent tourner plus rapidement que 4 aller-retour, en raison de la surcharge du réseau, et parce que vous devez vous arrêter et préparer et envoyer chaque requête après le la requête précédente est terminée. Si vous deviez comparer un serveur sous charge, cependant, dans la plupart des cas, les jointures prendront plus de temps CPU que les requêtes PK, et entraîneront souvent plus de surcharge du réseau.
mindplay.dk

97

Pour les jointures internes, une seule requête a du sens, car vous n'obtenez que les lignes correspondantes. Pour les jointures à gauche, plusieurs requêtes sont bien meilleures ... regardez le benchmark suivant que j'ai fait:

  1. Requête unique avec 5 jointures

    requête: 8,074508 secondes

    taille du résultat: 2268000

  2. 5 requêtes consécutives

    temps de requête combiné: 0,00262 secondes

    taille du résultat: 165 (6 + 50 + 7 + 12 + 90)

.

Notez que nous obtenons les mêmes résultats dans les deux cas (6 x 50 x 7 x 12 x 90 = 2268000)

les jointures à gauche utilisent exponentiellement plus de mémoire avec des données redondantes.

La limite de mémoire peut ne pas être aussi mauvaise si vous ne faites qu'une jointure de deux tables, mais généralement de trois ou plus et cela vaut la peine de requêtes différentes.

En remarque, mon serveur MySQL est juste à côté de mon serveur d'applications ... donc le temps de connexion est négligeable. Si votre temps de connexion est en secondes, il y a peut-être un avantage

Franc


31
Si nous mettons de côté le petit fait ennuyeux que personne dans son bon esprit ne fait une jointure croisée entre 5 tables (pour cette même raison, et dans la plupart des cas, cela n'a tout simplement pas de sens ), votre «référence» pourrait avoir un certain mérite . Mais les jointures à gauche ou internes sont la norme, généralement par clé (ce qui rend la récupération beaucoup plus rapide), et la duplication des données est généralement beaucoup, beaucoup moins que ce que vous prétendez être.
cHao

12
@cHao dit qui? Je viens de chercher SMF et phpBB et j'ai vu des JOINs entre 3 tables - si vous ajoutez des plugins ou des modifications, ils pourraient facilement ajouter à cela. Toute sorte de grande application a le potentiel pour de nombreuses JOIN. On peut soutenir qu'un ORM mal écrit / mal utilisé pourrait JOINDRE des tables dont il n'a pas réellement besoin (peut-être même chaque table).
Natalie Adams

5
@NathanAdams: Les jointures gauche et interne ne sont pas du tout mauvaises. (En fait, si vous ne joignez pas des tables ici et là, vous faites mal SQL.) Ce dont je parlais , ce sont les jointures croisées , qui sont presque toujours indésirables même entre deux tables, et encore moins 5 - et qui être à peu près le seul moyen d'obtenir les résultats "2268000" autrement totalement faux mentionnés ci-dessus.
cHao

2
Regardez les résultats, cependant. "taille du résultat: 2268000" contre "taille du résultat: 165". Je pense que votre ralentissement avec JOINs est dû au fait que vos enregistrements ont une relation un-à-plusieurs les uns avec les autres, alors que s'ils avaient une relation un-à-un, le JOIN serait absolument beaucoup plus rapide et il n'aurait certainement pas de résultat taille plus grande que le SELECT.
HoldOffHunger

3
@cHao Evidemment vous n'avez pas rencontré Magento lors de votre premier commentaire
vitoriodachef

26

Cette question est ancienne, mais il manque quelques repères. J'ai comparé JOIN à ses 2 concurrents:

  • Requêtes N + 1
  • 2 requêtes, la seconde utilisant un WHERE IN(...)ou équivalent

Le résultat est clair: sur MySQL, JOINc'est beaucoup plus rapide. Les requêtes N + 1 peuvent réduire considérablement les performances d'une application:

REJOINDRE vs WHERE IN vs N + 1

Autrement dit, sauf si vous sélectionnez un grand nombre d'enregistrements qui pointent vers un très petit nombre d'enregistrements étrangers distincts. Voici une référence pour le cas extrême:

JOIN vs N + 1 - tous les enregistrements pointant vers le même enregistrement étranger

Il est très peu probable que cela se produise dans une application typique, sauf si vous joignez une relation -to-plusieurs, auquel cas la clé étrangère se trouve sur l'autre table et que vous dupliquez les données de la table principale plusieurs fois.

À emporter:

  • Pour les relations *-à-un, utilisez toujours JOIN
  • Pour les relations * -à-plusieurs, une deuxième requête peut être plus rapide

Voir mon article sur Medium pour plus d'informations.


22

Je suis en fait venu à cette question à la recherche d'une réponse moi-même, et après avoir lu les réponses données, je ne peux que convenir que le meilleur moyen de comparer les performances des requêtes DB est d'obtenir des nombres réels car il y a juste trop de variables à prendre en compte MAIS, je pense aussi que comparer les chiffres entre eux ne mène à rien dans presque tous les cas. Ce que je veux dire, c'est que les nombres doivent toujours être comparés à un nombre acceptable et certainement pas comparés les uns aux autres.

Je peux comprendre que si une façon d'interroger prend par exemple 0,02 seconde et l'autre 20 secondes, c'est une énorme différence. Mais que se passe-t-il si une façon d'interroger prend 0,0000000002 secondes, et l'autre prend 0,0000002 secondes? Dans les deux cas, un chemin est 1000 fois plus rapide que l'autre, mais est-ce vraiment encore "énorme" dans le second cas?

En bout de ligne, comme je le vois personnellement: s'il fonctionne bien, optez pour la solution facile.


4
Cela, bien sûr, selon que vous prévoyez ou non d'évoluer. Parce que quand Facebook a commencé, je suis sûr qu'ils avaient ce genre de questions, mais avaient à l'esprit la mise à l'échelle et ont opté pour la solution la plus efficace, bien que peut-être plus complexe.
dudewad

@dudewad Cela a du sens. Tout dépend de ce dont vous avez besoin, en fin de compte.
Valentin Flachsel

4
Haha ouais ... parce que sur google, 1 nanoseconde perdue équivaut littéralement à quelque chose comme 10 milliards de milliards de dollars ... mais ce n'est qu'une rumeur.
dudewad

2
@dudewad En fait, quand Facebook a commencé, je vous garantis qu'ils ont opté pour la solution la plus simple. Zuckerberg a déclaré avoir programmé la première version en seulement 2 semaines. Les startups doivent agir rapidement pour être compétitives et celles qui survivent ne se soucient généralement pas de la mise à l'échelle jusqu'à ce qu'elles en aient réellement besoin. Ensuite, ils refactorisent les choses après avoir investi des millions de dollars et peuvent embaucher des programmeurs rockstar spécialisés dans la performance. Pour ce faire, je m'attendrais à ce que Facebook choisisse souvent la solution la plus complexe pour des gains de performances infimes maintenant, mais la plupart d'entre nous ne programmons pas Facebook.
dallin

15

J'ai effectué un test rapide en sélectionnant une ligne dans une table de 50 000 lignes et en la rejoignant avec une ligne dans une table de 100 000 lignes. Fondamentalement ressemblait à:

$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']);

contre

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

La méthode de sélection à deux a pris 3,7 secondes pour 50 000 lectures alors que le JOIN a pris 2,0 secondes sur mon ordinateur lent à la maison. INNER JOIN et LEFT JOIN n'ont pas fait de différence. La récupération de plusieurs lignes (par exemple, en utilisant IN SET) a donné des résultats similaires.


1
Peut-être que la différence pourrait tourner autrement si vous sélectionnez une page de lignes (comme 20 ou 50) comme pour une grille de vue Web typique, et comparez une seule jointure à gauche à deux requêtes - en sélectionnant 2 ou 3 identificateurs avec certains critères WHERE, puis en exécutant l'autre SELECT avec IN ().
JustAMartin le

Les colonnes id et other_id sont-elles indexées?
Aarish Ramesh

11

La vraie question est: ces enregistrements ont -ils une relation un-à-un ou une relation un-à-plusieurs ?

Réponse TLDR:

S'il s'agit d'un à un, utilisez une JOINinstruction.

S'il s'agit d'un à plusieurs, utilisez une (ou plusieurs) SELECTinstructions avec l'optimisation du code côté serveur.

Pourquoi et comment utiliser SELECT pour l'optimisation

SELECT'ing (avec plusieurs requêtes au lieu de jointures) sur un grand groupe d'enregistrements basé sur une relation un-à-plusieurs produit une efficacité optimale, car JOIN' ing a un problème de fuite de mémoire exponentielle. Saisissez toutes les données, puis utilisez un langage de script côté serveur pour les trier:

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

Résultats:

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"

Ici, je reçois tous les enregistrements, dans une seule instruction select. C'est mieux que JOIN, qui obtiendrait un petit groupe de ces enregistrements, un à la fois, en tant que sous-composant d'une autre requête. Ensuite, je l'analyse avec un code côté serveur qui ressemble à quelque chose comme ...

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

Quand ne pas utiliser JOIN pour l'optimisation

JOINUn grand groupe d'enregistrements basé sur une relation biunivoque avec un seul enregistrement produit une efficacité optimale par rapport à plusieurs SELECTinstructions, l'une après l'autre, qui obtiennent simplement le type d'enregistrement suivant.

Mais JOINest inefficace lors de l'obtention d'enregistrements avec une relation un-à-plusieurs.

Exemple: La base de données Blogs a 3 tables d'intérêt, Blogpost, Tag et Comment.

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

S'il y a 1 article de blog, 2 balises et 2 commentaires, vous obtiendrez des résultats comme:

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

Remarquez comment chaque enregistrement est dupliqué. Bon, donc, 2 commentaires et 2 balises font 4 lignes. Et si nous avons 4 commentaires et 4 balises? Vous n'obtenez pas 8 lignes - vous obtenez 16 lignes:

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,

Ajoutez plus de tables, plus d'enregistrements, etc., et le problème augmentera rapidement à des centaines de lignes qui sont toutes pleines de données pour la plupart redondantes.

Combien vous coûtent ces doublons? Mémoire (dans le serveur SQL et le code qui tente de supprimer les doublons) et ressources réseau (entre le serveur SQL et votre serveur de code).

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


Vous manquez le point. Il ne s'agit pas de un à un (un | plusieurs). Il s'agit de savoir si les ensembles de lignes ont du sens d'être jumelés. Vous ne demandez que deux ensembles de données liés de manière tangentielle. Si vous demandez des commentaires et, par exemple, les coordonnées de leurs auteurs, cela a plus de sens en tant que jointure, même si les gens peuvent vraisemblablement écrire plus d'un commentaire.
cHao

@cHao: Merci pour votre commentaire. Ma réponse ci-dessus est un résumé de la documentation MySQL trouvée ici: dev.mysql.com/doc/workbench/en/wb-relationship-tools.html
HoldOffHunger

Ce n'est pas de la documentation MySQL. C'est la documentation d'un outil GUI particulier pour travailler avec des bases de données MySQL. Et il n'offre aucune indication sur le moment où les jointures sont (ou ne sont pas) appropriées.
cHao

@cHao: Désolé, je voulais dire la documentation MySQL (R) pour MySQL WorkBench (TM), pas MySQL Server (TM).
HoldOffHunger

Mis à part la pédanterie, la pertinence n'est pas claire. Les deux mentionnent des relations un-à-un et un-à-plusieurs, mais c'est là que se termine le point commun. Dans tous les cas, le problème concerne la relation entre les ensembles de données. Rejoignez deux ensembles indépendants, vous obtiendrez toutes les combinaisons des deux. Divisez les données associées en plusieurs sélections, et maintenant vous avez effectué plusieurs requêtes pour un avantage douteux et avez commencé à faire le travail de MySQL pour cela.
cHao

8

Construisez à la fois des requêtes et des jointures séparées, puis chronométrez chacune d'elles - rien ne sert plus que des nombres réels.

Alors encore mieux - ajoutez "EXPLAIN" au début de chaque requête. Cela vous indiquera combien de sous-requêtes MySQL utilise pour répondre à votre demande de données, et combien de lignes analysées pour chaque requête.


7

En fonction de la complexité de la base de données par rapport à la complexité du développeur, il peut être plus simple d'effectuer de nombreux appels SELECT.

Essayez d'exécuter des statistiques de base de données sur le JOIN et les multiples SELECTS. Voyez si dans votre environnement le JOIN est plus rapide / plus lent que le SELECT.

Là encore, si le changer en JOIN signifierait un jour / semaine / mois supplémentaire de travail de développement, je m'en tiendrai à plusieurs SELECT

À votre santé,

BLT


5

D'après mon expérience, j'ai trouvé qu'il est généralement plus rapide d'exécuter plusieurs requêtes, en particulier lors de la récupération de grands ensembles de données.

Lors de l'interaction avec la base de données à partir d'une autre application, telle que PHP, il y a l'argument d'un voyage vers le serveur sur plusieurs.

Il existe d'autres moyens de limiter le nombre de trajets effectués sur le serveur et d'exécuter encore plusieurs requêtes qui sont souvent non seulement plus rapides, mais facilitent également la lecture de l'application - par exemple mysqli_multi_query.

Je ne suis pas novice en matière de SQL, je pense que les développeurs, en particulier les juniors, ont tendance à passer beaucoup de temps à essayer d'écrire des jointures très intelligentes parce qu'elles ont l'air intelligentes, alors qu'il existe en fait des moyens intelligents d'extraire des données qui semblent Facile.

Le dernier paragraphe était une opinion personnelle, mais j'espère que cela aide. Je suis cependant d'accord avec les autres qui disent que vous devriez évaluer. Aucune des deux approches n'est une solution miracle.


Oui, nous devons également tenir compte non seulement des requêtes elles-mêmes, mais également du traitement des données au sein de l'application. Si vous récupérez des données avec des jointures externes, il existe une certaine redondance (parfois elle peut devenir vraiment énorme) qui doit être triée par l'application (généralement dans une bibliothèque ORM), donc en résumé, la seule requête SELECT avec JOIN pourrait consommer plus de CPU et temps que deux simples SELECTs
JustAMartin

4

Si vous utilisez une jointure est d' abord et avant tout de savoir si une jointure est logique . Ce n'est qu'à ce stade que les performances doivent être prises en compte, car presque tous les autres cas entraîneront des performances nettement moins bonnes.

Les différences de performances seront en grande partie liées à la relation entre les informations que vous recherchez. Les jointures fonctionnent et elles sont rapides lorsque les données sont liées et que vous indexez correctement les éléments, mais elles entraînent souvent une certaine redondance et parfois plus de résultats que nécessaire. Et si vos ensembles de données ne sont pas directement liés, les coller dans une seule requête aboutira à ce qu'on appelle un produit cartésien (en gros, toutes les combinaisons possibles de lignes), ce qui n'est presque jamais ce que vous voulez.

Ceci est souvent causé par des relations plusieurs-à-un-à-plusieurs. Par exemple, la réponse de HoldOffHunger mentionnait une seule requête pour les publications, les tags et les commentaires. Les commentaires sont liés à un article, tout comme les balises ... mais les balises ne sont pas liées aux commentaires.

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

Dans ce cas, il est clairement préférable que ce soit au moins deux requêtes distinctes. Si vous essayez de joindre des balises et des commentaires, car il n'y a pas de relation directe entre les deux, vous vous retrouvez avec toutes les combinaisons possibles de balise et de commentaire. many * many == manymany. En dehors de cela, comme les publications et les tags ne sont pas liés, vous pouvez effectuer ces deux requêtes en parallèle, ce qui entraîne un gain potentiel.

Considérons un scénario différent, cependant: vous voulez que les commentaires soient joints à un article et les coordonnées des commentateurs.

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

C'est là que vous devriez envisager une jointure. En plus d'être une requête beaucoup plus naturelle, la plupart des systèmes de base de données (y compris MySQL) ont beaucoup de gens intelligents qui travaillent dur pour optimiser les requêtes comme ça. Pour les requêtes séparées, puisque chaque requête dépend des résultats de la précédente, les requêtes ne peuvent pas être effectuées en parallèle et le temps total devient non seulement le temps réel d'exécution des requêtes, mais également le temps passé à récupérer les résultats, à passer au crible à travers eux pour les identifiants de la requête suivante, reliant les lignes entre elles, etc.


Si vous récupérez beaucoup de colonnes utilisateur dans le deuxième scénario (et que les mêmes utilisateurs commentent plus d'une fois), cela laisse toujours ouverte la question de savoir s'il est préférable de les récupérer dans une requête distincte.
Adrian Baker

@AdrianBaker: Comme je l'ai dit, beaucoup de gens intelligents font beaucoup de travail. Si j'allais optimiser mon serveur SQL, ma toute première idée serait d'utiliser la compression, ce qui éliminerait une énorme quantité de redondance sans changer le code beaucoup du tout. Les optimisations de niveau suivant incluraient la réorganisation du résultat en tables et l'envoi de celles-ci avec des tuples d'ID de ligne, que la bibliothèque cliente pourrait ensuite facilement assembler sur son côté si nécessaire.
cHao

Ces deux optimisations pourraient faire des merveilles avec une jointure pour réduire ou même éliminer la redondance, mais il n'y a pas grand-chose qui puisse aider avec les requêtes en série intrinsèques que vous auriez à faire pour récupérer les enregistrements associés.
cHao

3

Sera-ce plus rapide en termes de débit? Probablement. Mais il verrouille également potentiellement plus d'objets de base de données à la fois (en fonction de votre base de données et de votre schéma) et diminue ainsi la concurrence. D'après mon expérience, les gens sont souvent induits en erreur par l'argument "moins d'aller-retour de base de données" alors qu'en réalité sur la plupart des systèmes OLTP où la base de données est sur le même LAN, le véritable goulot d'étranglement est rarement le réseau.


2

Voici un lien avec 100 requêtes utiles, celles-ci sont testées dans la base de données Oracle mais rappelez-vous que SQL est une norme, ce qui diffère entre Oracle, MS SQL Server, MySQL et d'autres bases de données est le dialecte SQL:

http://javaforlearn.com/100-sql-queries-learn/


1

Il y a plusieurs facteurs qui signifient qu'il n'y a pas de réponse binaire. La question de savoir ce qui est le mieux pour les performances dépend de votre environnement. À propos, si votre sélection unique avec un identifiant n'est pas inférieure à la seconde, il se peut que quelque chose ne tourne pas rond avec votre configuration.

La vraie question à se poser est de savoir comment voulez-vous accéder aux données. Les sélections uniques prennent en charge la liaison tardive. Par exemple, si vous souhaitez uniquement des informations sur les employés, vous pouvez effectuer une sélection dans la table Employés. Les relations de clé étrangère peuvent être utilisées pour récupérer les ressources associées ultérieurement et selon les besoins. Les sélections auront déjà une clé sur laquelle pointer, elles devraient donc être extrêmement rapides et vous n'avez qu'à récupérer ce dont vous avez besoin. La latence du réseau doit toujours être prise en compte.

Les jointures récupéreront toutes les données à la fois. Si vous générez un rapport ou remplissez une grille, c'est peut-être exactement ce que vous voulez. Les jointures compilées et optimisées seront tout simplement plus rapides que les sélections uniques dans ce scénario. N'oubliez pas que les jointures ad hoc peuvent ne pas être aussi rapides - vous devez les compiler (dans un processus stocké). La réponse rapide dépend du plan d'exécution, qui détaille exactement les étapes suivies par le SGBD pour récupérer les données.


0

Oui, une requête utilisant JOINS serait plus rapide. Bien que sans connaître les relations des tables que vous interrogez, la taille de votre ensemble de données ou l'emplacement des clés primaires, il est presque impossible de dire combien plus rapidement.

Pourquoi ne pas tester les deux scénarios, alors vous saurez avec certitude ...

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.