Quand utiliser STRAIGHT_JOIN avec MySQL


88

J'avais juste une requête assez complexe avec laquelle je travaillais et cela prenait 8 secondes pour s'exécuter. EXPLAIN montrait un ordre de table étrange et mes index n'étaient pas tous utilisés, même avec l'indication FORCE INDEX. Je suis tombé sur le mot clé de jointure STRAIGHT_JOIN et j'ai commencé à remplacer certains de mes mots clés INNER JOIN par lui. J'ai remarqué une amélioration considérable de la vitesse. Finalement, je viens de remplacer tous mes mots clés INNER JOIN par STRAIGHT_JOIN pour cette requête et elle s'exécute maintenant en 0,01 seconde.

Ma question est de savoir quand utilisez-vous STRAIGHT_JOIN et quand utilisez-vous INNER JOIN? Y a-t-il une raison de ne pas utiliser STRAIGHT_JOIN si vous écrivez de bonnes requêtes?

Réponses:


73

Je ne recommanderais pas d'utiliser STRAIGHT_JOIN sans une bonne raison. Ma propre expérience est que l'optimiseur de requêtes MySQL choisit un plan de requête médiocre plus souvent que je ne le souhaiterais, mais pas assez souvent pour que vous deviez simplement le contourner en général, ce que vous feriez si vous utilisiez toujours STRAIGHT_JOIN.

Ma recommandation est de laisser toutes les requêtes comme des JOINs réguliers. Si vous découvrez qu'une requête utilise un plan de requête sous-optimal, je vous suggère d'abord d'essayer de réécrire ou de restructurer un peu la requête pour voir si l'optimiseur choisira alors un meilleur plan de requête. Aussi, pour innodb au moins, assurez-vous que ce n'est pas seulement que vos statistiques d'index ne sont pas à jour ( ANALYZE TABLE ). Cela peut amener l'optimiseur à choisir un plan de requête médiocre. Les conseils d'optimisation doivent généralement être votre dernier recours.

Une autre raison de ne pas utiliser les conseils de requête est que votre distribution de données peut changer avec le temps, ou votre sélectivité d'index peut changer, etc. à mesure que votre table grandit. Vos conseils de requête qui sont optimaux actuellement peuvent devenir sous-optimaux avec le temps. Mais l'optimiseur ne pourra pas adapter le plan de requête en raison de vos indications désormais obsolètes. Vous restez plus flexible si vous autorisez l'optimiseur à prendre les décisions.


59
Cette réponse n'explique pas vraiment quand l'utiliser straight_join .
Pacerier

23

À partir de la référence MySQL JOIN :

"STRAIGHT_JOIN est similaire à JOIN, sauf que la table de gauche est toujours lue avant la table de droite. Cela peut être utilisé pour les (quelques) cas pour lesquels l'optimiseur de jointure place les tables dans le mauvais ordre."


27
Merci, mais j'ai déjà lu le manuel MySQL dessus. En espérant des explications supplémentaires.
Greg

20

Voici un scénario qui est apparu récemment au travail.

Considérez trois tableaux, A, B, C.

A a 3 000 lignes; B a 300 000 000 lignes; et C a 2 000 lignes.

Les clés étrangères sont définies: B (a_id), B (c_id).

Supposons que vous ayez une requête qui ressemble à ceci:

select a.id, c.id
from a
join b on b.a_id = a.id
join c on c.id = b.c_id

D'après mon expérience, MySQL peut choisir d'aller C -> B -> A dans ce cas. C est plus petit que A et B est énorme, et ce sont tous des équivalents.

Le problème est que MySQL ne prend pas nécessairement en compte la taille de l'intersection entre (C.id et B.c_id) vs (A.id et B.a_id). Si la jointure entre B et C renvoie autant de lignes que B, alors c'est un très mauvais choix; si commencer par A aurait filtré B jusqu'à autant de lignes que A, alors cela aurait été un bien meilleur choix. straight_joinpourrait être utilisé pour forcer cet ordre comme ceci:

select a.id, c.id
from a
straight_join b on b.a_id = a.id
join c on c.id = b.c_id

Maintenant adoit être rejoint avant b.

En règle générale, vous souhaitez effectuer vos jointures dans un ordre qui minimise le nombre de lignes dans l'ensemble résultant. Donc, commencer par une petite table et joindre de telle sorte que la jointure résultante soit également petite est idéal. Les choses prennent la forme d'une poire si commencer par une petite table et la joindre à une plus grande table finit par être aussi grande que la grande table.

Cela dépend cependant des statistiques. Si la distribution des données change, le calcul peut changer. Cela dépend également des détails d'implémentation du mécanisme de jointure.

Les pires cas que j'ai vus pour MySQL où tous straight_joinles indices d'index obligatoires ou agressifs sont des requêtes qui paginent sur un grand nombre de données dans un ordre de tri strict avec un filtrage de la lumière. MySQL préfère fortement utiliser des index pour tous les filtres et jointures sur les tris; cela a du sens car la plupart des gens n'essaient pas de trier toute la base de données mais ont plutôt un sous-ensemble limité de lignes qui répondent à la requête, et le tri d'un sous-ensemble limité est beaucoup plus rapide que de filtrer la table entière, qu'elle soit triée ou ne pas. Dans ce cas, mettre une jointure directe immédiatement après la table qui avait la colonne indexée que je voulais trier sur des choses fixes.


Comment utiliseriez-vous la jointure directe pour résoudre le problème?
Hannele

@Hannele straight_joinévalue la table de gauche avant la droite. Donc, si vous voulez partir de A -> B -> Cmon exemple, le premier joinmot-clé pourrait être remplacé par straight_join.
Barry Kelly

Ah bien. Il serait utile d'inclure cela comme exemple dans votre réponse :)
Hannele

18

MySQL n'est pas nécessairement bon pour choisir l'ordre de jointure dans les requêtes complexes. En spécifiant une requête complexe en tant que straight_join, la requête exécute les jointures dans l'ordre dans lequel elles sont spécifiées. En plaçant la table comme le plus petit dénominateur commun en premier et en spécifiant straight_join, vous pouvez améliorer les performances de la requête.


11

STRAIGHT_JOIN, en utilisant cette clause, vous pouvez contrôler l' JOINordre: quelle table est analysée dans la boucle externe et laquelle est dans la boucle interne.


Que sont la boucle extérieure et la boucle intérieure?
Istiaque Ahmed

Les tables @IstiaqueAhmed sont reliées par des boucles imbriquées (prenez la première ligne de la table A et la boucle lancez la table B puis prenez la deuxième ligne ... et ainsi de suite. Ici la table A est à la boucle externe)
Comptable du

6

Je vais vous expliquer pourquoi j'ai dû utiliser STRAIGHT_JOIN:

  • J'ai eu un problème de performances avec une requête.
  • Simplifiant la requête, la requête était soudainement plus efficace
  • En essayant de comprendre quelle partie spécifique posait le problème, je ne pouvais tout simplement pas. (2 jointures à gauche étaient lentes et chacune était indépendamment rapide)
  • J'ai ensuite exécuté EXPLAIN avec une requête lente et rapide (ajoutez l'une des jointures de gauche)
  • Étonnamment, MySQL a entièrement changé les commandes JOIN entre les 2 requêtes.

Par conséquent, j'ai forcé l'une des jointures à être straight_join pour FORCE la jointure précédente à lire en premier. Cela a empêché MySQL de changer l'ordre d'exécution et a fonctionné comme un charme!


2

Dans ma courte expérience, l'une des situations qui STRAIGHT_JOINa réduit ma requête de 30 secondes à 100 millisecondes est que la première table du plan d'exécution n'était pas la table qui a l'ordre par colonnes

-- table sales (45000000) rows
-- table stores (3) rows
SELECT whatever
FROM 
    sales 
    INNER JOIN stores ON sales.storeId = stores.id
ORDER BY sales.date, sales.id 
LIMIT 50;
-- there is an index on (date, id)

SI l'optimiseur choisit de frapper en stores premier, cela provoquera Using index; Using temporary; Using filesortcar

si ORDER BY ou GROUP BY contient des colonnes de tables autres que la première table de la file d'attente de jointure, une table temporaire est créée.

la source

ici l'optimiseur a besoin d'un peu d'aide en lui disant de frapper d' salesabord en utilisant

sales STRAIGHT_JOIN stores

1
(J'ai embelli votre réponse.)
Rick James

2

Si votre requête se termine par ORDER BY... LIMIT..., il peut être optimal de reformuler la requête pour inciter l'optimiseur à faire le LIMIT avant le JOIN.

(Cette réponse ne s'applique pas uniquement à la question initiale sur STRAIGHT_JOIN, ni à tous les cas de STRAIGHT_JOIN.)

En commençant par l' exemple de @Accountant م , cela devrait fonctionner plus rapidement dans la plupart des situations. (Et cela évite d'avoir besoin d'indices.)

SELECT  whatever
    FROM  ( SELECT id FROM sales
                ORDER BY  date, id
                LIMIT  50
          ) AS x
    JOIN  sales   ON sales.id = x.id
    JOIN  stores  ON sales.storeId = stores.id
    ORDER BY  sales.date, sales.id;

Remarques:

  • Tout d'abord, 50 identifiants sont récupérés. Ce sera particulièrement rapide avec INDEX(date, id).
  • Ensuite, la jointure vers salesvous permet d'obtenir seulement 50 "whatevers" sans les transporter dans une table temporaire.
  • comme une sous-requête n'est, par définition, pas ordonnée, le ORDER BYdoit être répété dans la requête externe. (L'Optimiseur peut trouver un moyen d'éviter de faire un autre tri.)
  • Oui, c'est plus salissant. Mais c'est généralement plus rapide.

Je m'oppose à l'utilisation des hits parce que «même si c'est plus rapide aujourd'hui, ça risque de ne pas être plus rapide demain».


0

Je sais que c'est un peu vieux mais voici un scénario, j'ai fait un script batch pour peupler une certaine table. À un moment donné, la requête s'est déroulée très lentement. Il semble que l'ordre de jointure était incorrect sur des enregistrements particuliers:

  • Dans le bon ordre

entrez la description de l'image ici

  • Incrémenter l'id de 1 perturbe la commande. Remarquez le champ 'Extra'

entrez la description de l'image ici

  • L'utilisation de straight_join résout le problème

entrez la description de l'image ici

Une commande incorrecte s'exécute pendant environ 65 secondes lors de l'utilisation de straight_join s'exécute en millisecondes


-5
--use 120s, 18 million data
    explain SELECT DISTINCT d.taid
    FROM tvassist_recommend_list_everyday_diverse d, tvassist_taid_all t
    WHERE d.taid = t.taid
      AND t.client_version >= '21004007'
      AND t.utdid IS NOT NULL
      AND d.recommend_day = '20170403'
    LIMIT 0, 10000

--use 3.6s repalce by straight join
 explain SELECT DISTINCT d.taid
    FROM tvassist_recommend_list_everyday_diverse d
    STRAIGHT_JOIN 
      tvassist_taid_all t on d.taid = t.taid 
    WHERE 
     t.client_version >= '21004007'
       AND d.recommend_day = '20170403'

      AND t.utdid IS NOT NULL  
    LIMIT 0, 10000

3
Cela ne vous donne pas assez d'informations pour savoir quand les jointures droites sont appropriées.
Hannele
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.