2008-10-09 4 views
15

J'ai profilé certaines requêtes dans une application sur laquelle je travaille, et je suis tombé sur une requête qui récupérait plus de lignes que nécessaire, le jeu de résultats étant réduit dans le code de l'application.Gauche Joignez-vous à la fonction Inner Join?

La modification d'un jointure gauche à une jointure interne a réduit l'ensemble de résultats à ce qui était nécessaire et serait probablement plus performant (puisque moins de lignes sont sélectionnées). En réalité, la requête LEFT JOIN'ed surpassait l'INNER JOIN'ed, prenant la moitié du temps pour se terminer.

LEFT JOIN: (127 lignes au total, la requête a 0,0011 s)

INNER JOIN: (10 lignes au total, la requête a 0,0024 s)

(j'ai couru les requêtes à plusieurs reprises et ce sont des moyennes) .

Courir EXPLIQUEZ sur les deux révèle rien qui explique les différences de performance:

Pour la INNER JOIN:

id select_type  table type possible_keys key  key_len  ref  rows  Extra 
1 SIMPLE contacts  index  NULL  name  302  NULL   235 Using where 
1 SIMPLE lists   eq_ref  PRIMARY  PRIMARY  4 contacts.list_id  1 
1 SIMPLE lists_to_users eq_ref  PRIMARY  PRIMARY  8 lists.id,const 1  
1 SIMPLE tags   eq_ref  PRIMARY  PRIMARY  4 lists_to_users.tag_id 1  
1 SIMPLE users   eq_ref  email_2  email_2  302  contacts.email 1 Using where 

Pour LEFT JOIN:

id select_type  table type possible_keys key  key_len  ref  rows Extra 
1 SIMPLE   contacts index  NULL  name  302  NULL 235  Using where 
1 SIMPLE  lists  eq_ref  PRIMARY  PRIMARY  4 contacts.list_id 1  
1 SIMPLE lists_to_users eq_ref  PRIMARY  PRIMARY  8 lists.id,const 1  
1 SIMPLE   tags  eq_ref  PRIMARY  PRIMARY  4 lists_to_users.tag_id 1  
1 SIMPLE  users  eq_ref  email_2  email_2  302  contacts.email 1 

Et la requête elle-même:

SELECT `contacts`.*, `lists`.`name` AS `group`, `lists`.`id` AS `group_id`, `lists`.`shared_yn`, `tags`.`name` AS `context`, `tags`.`id` AS `context_id`, `tags`.`color` AS `context_color`, `users`.`id` AS `user_id`, `users`.`avatar` 
FROM `contacts` 
LEFT JOIN `lists` ON lists.id=contacts.list_id 
LEFT JOIN `lists_to_users` ON lists_to_users.list_id=lists.id AND lists_to_users.user_id='1' AND lists_to_users.creator='1' 
LEFT JOIN `tags` ON tags.id=lists_to_users.tag_id 
INNER JOIN `users` ON users.email=contacts.email 
WHERE (contacts.user_id='1') 
ORDER BY `contacts`.`name` ASC 

(La clause dont je parle est la dernière INNER JOIN dans la table 'users')

La requête s'exécute sur une base de données MySQL 5.1, si cela fait une différence.

Est-ce que quelqu'un a une idée de la raison pour laquelle la requête LEFT JOIN'ed surpasse l'INNER JOIN'ed dans ce cas? En raison de la suggestion de Tomalak que les petites tables que j'utilise rendaient INNER JOIN plus complexe, j'avais créé une base de données de test avec quelques données fictives. La table 'users' contient 5 000 lignes et la table de contacts environ 500 000 lignes. Les résultats sont les mêmes (aussi les timings n'ont pas changé ce qui est surprenant quand on considère que les tableaux sont beaucoup plus gros maintenant). J'ai également lancé ANALYZE et OPTIMIZE sur la table de contacts. N'a pas fait de différence discernable.

+0

Avez-vous essayé de placer la jointure intérieure en premier? –

+0

J'ai, il accélère cette requête de 20%, mais toujours plus lent que LEFT JOIN –

+0

Essayez de construire chaque requête de manière séquentielle (joindre une table, mesurer, joindre la suivante, etc.) Peut-être cela vous aide à déterminer la lenteur . – Tomalak

Répondre

6

Il est probable que INNER JOIN doive vérifier chaque ligne dans les deux tables pour voir si les valeurs de colonne (email dans votre cas) correspondent. Le LEFT JOIN retournera tout d'une table indépendamment. S'il est indexé, il saura aussi quoi faire plus vite.

+0

J'ai essayé d'utiliser un index sur la colonne e-mail, et un index combiné sur les colonnes name + email, mais le plan d'exécution de requête reste le même –

+0

Cela aidera les jointures INNER et LEFT I devinez, donc je n'aurais pas pensé que cela ferait un plus vite que l'autre en le faisant. – HAdes

+3

La jointure interne analyse une table et trouve des lignes correspondantes dans l'autre, idéalement en utilisant et indexant pour cela. Il n'est pas nécessaire de vérifier chaque rangée dans les deux tableaux comme vous le suggérez. – Tomalak

4

La cardinalité de table a une influence sur l'optimiseur de requête. Je suppose que les petites tables, comme vous avez fait l'interne, se joignent à l'opération la plus complexe. Dès que vous avez plus d'enregistrements que le serveur de base de données ne veut conserver en mémoire, la jointure interne commencera probablement à surpasser la jointure à gauche.

+0

C'est intéressant. Je vais devoir vérifier sur un plus grand ensemble et voir si cela fonctionne comme vous l'avez décrit. –

+0

J'ai re-couru avec des tables beaucoup plus grandes et les résultats sont les mêmes. –

+0

+1 sur la réponse . @ Eran Galperin ive lire votre note sur votre question et les tables dont vous parlez, ne sont pas "grand" du tout. Avec le matériel d'aujourd'hui, vous avez besoin de tables avec des millions de lignes, quand on parle de grandes tables mate. – kommradHomer

2

vous tombez dans l'écueil connu sous le nom d'optimisation prématurée. Les optimiseurs de requêtes sont des choses incroyablement inconstantes. Ma suggestion, est de continuer jusqu'à ce que vous puissiez identifier avec certitude que la jointure particulière est problématique.

+1

Il ne s'agit pas d'optimisation, mais de comprendre pourquoi la requête se comporte d'une certaine manière. –

-3

LEFT JOIN retourne plus de lignes que INNER JOIN parce que ces 2 sont différents.
Si LEFT JOIN ne trouve pas d'entrée associée dans la table recherchée, elle retournera des valeurs NULL pour la table.
Mais si INNER JOIN ne trouve pas d'entrée associée, il ne renverra pas du tout la ligne .

Mais à votre question, avez-vous activé query_cache? Essayez d'exécuter la requête avec

SELECT SQL_NO_CACHE `contacts`.*, ... 

Autre que cela, je alimenter les tables avec plus de données, couru

ANALYZE TABLE t1, t2; 
OPTIMIZE TABLE t1, t2; 

Et voir ce qui se passe.

+0

Bien sûr, la jointure gauche renvoie plus de lignes, ce n'est pas le point de la question. Pourquoi ça marche plus vite TANDIS de retourner plus de lignes c'est ce qui me dépasse –

12

Si vous pensez que l'implémentation de LEFT JOIN est INNER JOIN + plus de travail, alors ce résultat est déroutant. Et si l'implémentation de INNER JOIN est (LEFT JOIN + filtrage)? Ah, c'est clair maintenant.

Dans les plans de requête, la seule différence est la suivante: users ... extra: using where. Cela signifie le filtrage. Il y a une étape de filtrage supplémentaire dans la requête avec la jointure interne.


Il s'agit d'un type de filtrage différent de celui généralement utilisé dans une clause where. Il est simple de créer un index sur A pour prendre en charge cette action de filtrage.

SELECT * 
FROM A 
WHERE A.ID = 3 

Tenir compte de cette requête:

SELECT * 
FROM A 
    LEFT JOIN B 
    ON A.ID = B.ID 
WHERE B.ID is not null 

Cette requête est équivalente à jointure interne. Il n'y a pas d'index sur B qui aidera cette action de filtrage. La raison en est que la clause where indiquant une condition sur le résultat de la jointure, au lieu d'une condition sur B.

+0

Je suis conscient de la différence entre une jointure gauche et une jointure interne. Vous pouvez dire la même chose à propos de la clause WHERE, cependant les requêtes filtrées avec une clause where prennent généralement beaucoup moins de temps à calculer. –

+0

J'ai lu ce que vous avez ajouté, et bien que je pense que vous pourriez être sur quelque chose avec l'étape de filtrage supplémentaire, je pense que vous êtes hors cible pour savoir pourquoi. Il y a un index sur la colonne de filtrage supplémentaire 'email' (qui est utilisé), donc il devrait être assez rapide pour améliorer les performances. –

+1

Oui, l'index sur le courrier électronique aide la jointure à gauche. Non, l'index sur le courrier électronique n'autorise pas le filtrage rapide des résultats post-jointure. –

0

Essayez ceci:

SELECT `contacts`.*, `lists`.`name` AS `group`, `lists`.`id` AS `group_id`, `lists`.`shared_yn`, `tags`.`name` AS `context`, `tags`.`id` AS `context_id`, `tags`.`color` AS `context_color`, `users`.`id` AS `user_id`, `users`.`avatar` 
FROM `contacts` 
INNER JOIN `users` ON contacts.user_id='1' AND users.email=contacts.email 
LEFT JOIN `lists` ON lists.id=contacts.list_id 
LEFT JOIN `lists_to_users` ON lists_to_users.user_id='1' AND lists_to_users.creator='1' AND lists_to_users.list_id=lists.id 
LEFT JOIN `tags` ON tags.id=lists_to_users.tag_id 
ORDER BY `contacts`.`name` ASC 

Cela devrait vous donner une performance supplémentaire parce que:

  • Vous mettez toutes les jointures internes avant que toute jointure "gauche" ou "droite" n'apparaisse. Cela filtre certains enregistrements avant d'appliquer les jointures externes suivantes
  • Le court-circuit des opérateurs "ET" (l'ordre des questions "ET"). Si la comparaison entre les colonnes et les littéraux est fausse, il n'exécutera pas le balayage de table requis pour la comparaison entre les tables PK et FK

Si vous ne trouvez aucune amélioration de performance, remplacez tous les columnset pour un "COUNT (*)" et faites vos tests internes/gauches. De cette façon, quelle que soit la requête, vous récupérera seulement 1 seule ligne avec 1 seule colonne (le nombre), de sorte que vous pouvez jeter que le nombre d'octets renvoyés est la cause de la lenteur de votre requête:

SELECT COUNT(*) 
FROM `contacts` 
INNER JOIN `users` ON contacts.user_id='1' AND users.email=contacts.email 
LEFT JOIN `lists` ON lists.id=contacts.list_id 
LEFT JOIN `lists_to_users` ON lists_to_users.user_id='1' AND lists_to_users.creator='1' AND lists_to_users.list_id=lists.id 
LEFT JOIN `tags` ON tags.id=lists_to_users.tag_id 

Bonne chance

Questions connexes