Je venais d'avoir 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'indice FORCE INDEX. Je suis tombé sur le mot clé STRAIGHT_JOIN join et j'ai commencé à remplacer certains de mes mots clés INNER JOIN par celui-ci. 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 il s'exécute désormais 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?
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 le contourniez en général, ce que vous feriez si vous utilisiez toujours STRAIGHT_JOIN.
Ma recommandation est de laisser toutes les requêtes en tant que JOIN régulières. Si vous découvrez qu'une requête utilise un plan de requête sous-optimal, je suggère d'abord d'essayer de réécrire ou de restructurer un peu la requête pour voir si l'optimiseur choisira ensuite un meilleur plan de requête. De plus, pour innodb au moins, assurez-vous que ce n'est pas seulement que vos statistiques d'index sont obsolètes ( ANALYZE TABLE ). Cela peut amener l'optimiseur à choisir un mauvais plan de requête. Les conseils d'optimisation devraient généralement être votre dernier recours.
Une autre raison de ne pas utiliser d'indices de requête est que votre distribution de données peut changer au fil du temps, ou votre sélectivité d'index peut changer, etc. à mesure que votre table se développe. Vos indices de requête qui sont optimaux maintenant, peuvent devenir sous-optimaux au fil du temps. Mais l'optimiseur ne pourra pas adapter le plan de requête en raison de vos conseils désormais obsolètes. Vous restez plus flexible si vous laissez l'optimiseur prendre les décisions.
De 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."
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 jointure directe, la requête exécute les jointures dans l'ordre où elles sont spécifiées. En plaçant d'abord le tableau comme le dénominateur le moins commun et en spécifiant straight_join, vous pouvez améliorer les performances de la requête.
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 eu 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 équijoins.
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 en autant de lignes que A, alors cela aurait été un bien meilleur choix. straight_join
pourrait ê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, a
doit être joint 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 avec une petite table et joindre de telle sorte que la jointure résultante sera également petite, est idéal. Les choses deviennent en forme de poire si vous commencez avec une petite table et que vous la joignez à une plus grande table finit tout aussi grande que la grande table.
Cela dépend 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 qui ont presque requis straight_join
ou l'indexation agressive d'index sont des requêtes qui paginent sur un grand nombre de données dans un ordre de tri strict avec un filtrage léger. MySQL préfère fortement utiliser les index pour tous les filtres et jointures plutôt que les tris; cela a du sens car la plupart des gens n'essaient pas de trier la base de données entière 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 le filtrage de la table entière, qu'elle soit triée ou ne pas. Dans ce cas, en plaçant la jointure droite immédiatement après la table qui avait la colonne indexée, je voulais trier les choses fixes.
STRAIGHT_JOIN
, en utilisant cette clause, vous pouvez contrôler l'ordre JOIN
: quelle table est analysée dans la boucle externe et laquelle se trouve dans la boucle interne.
Je vais vous expliquer pourquoi j'ai dû utiliser STRAIGHT_JOIN:
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!
Si votre requête se termine par ORDER BY... LIMIT...
, Il peut être optimal pour reformuler la requête afin d'inciter l'optimiseur à faire le LIMIT
avant le JOIN
.
(Cette réponse ne s'applique pas uniquement à la question d'origine sur STRAIGHT_JOIN
, Ni à tous les cas de STRAIGHT_JOIN
.)
En commençant par l'exemple de @Accountant م , cela devrait s'exécuter 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:
INDEX(date, id)
.sales
vous permet d'obtenir seulement 50 "whatevers" sans les transporter dans une table temporaire.ORDER BY
doit être répété dans la requête externe. (L'optimiseur peut trouver un moyen d'éviter de faire un autre tri.)Je suis opposé à l'utilisation de hits parce que "Même si c'est plus rapide aujourd'hui, ça risque de ne pas l'être demain."
Dans ma courte expérience, une des situations qui STRAIGHT_JOIN
a réduit ma requête de 30 secondes à 100 millisecondes, c'est que la première table du plan d'exécution n'était pas la table contenant 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 stores
en premier , cela causera Using index; Using temporary; Using filesort
car
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.
ici, l'optimiseur a besoin d'un peu d'aide en lui disant de frapper sales
d'abord en utilisant
sales STRAIGHT_JOIN stores