Optimisation requêtes : jointures / index ?

  • Auteur de la discussion Auteur de la discussion noren
  • Date de début Date de début
WRInaute accro
Salut

Alors je vais revenir sur un problème que j'avais déjà mentionné dans un topic en essayant d'être un peu plus précis

Si vous avez des tags sur vos sites, ou si vos articles peuvent être présents dans plusieurs catégories, vous avez bien une table de relation comme t_taxon_article (ou t_categorie_article ou t_tag_article) ? table ou vous mettez l'id de l'article et l'id du taxon (categorie, tag etc.) associé.

Dans ce cas comment faites vous vos requêtes et vos jointures pour afficher les articles de tel tag ou tel categorie ordonné d'une certaine façon (order by) ?
Et quels indexes utilisez vous ?

Voici mon problème.

J'ai actuellement ces 3 tables :

Code:
t_articles

id_article
titre
contenu
….
date_modif
en_ligne


t_taxon_article

id_article
id_taxon


t_taxon

id_taxon
taxon
taxonomie

taxonomie = tag, categorie....

impossible d'arriver a optimiser ce type de requête :

Code:
SELECT a.id_article FROM t_article as a
INNER JOIN t_taxon_article as ta ON (ta.id_article=a.id_article)
WHERE  ta.id_taxon=44
ORDER BY a.date_modif  LIMIT 100,10
pourquoi ? Tout simplement parce que mysql ne peut pas utiliser d'index étant donné que dans ma clause where et order by je fais appel a 2 tables différentes. L'idéal aurait été de créer un index combiné (ta.id_taxon, a.date_modif) ce qui est impossible.

Je me retrouve avec des « Using temporary; Using filesort  »  lorsque je vérifie avec EXPLAIN

L'une des solution (requête imbriquée) qui ne me donne pas entière satisfaction est la suivante :

Code:
SELECT a.id_article 
FROM t_article as a
WHERE EXISTS (
 SELECT 1 FROM t_taxon_article AS ta
 WHERE ta.id_taxon=44 AND ta.id_article=a.id_article)
ORDER BY a.date_modif DESC
LIMIT 100,10

En regardant avec EXPLAIN, j'ai bien des « Using where; Using index » et pourtant a certains moment en fonction du nombre d'articles à retourner, du nombre d'articles impactés etc (ou lorsque je viens de lancer mon serveur mysql). la requête grimpe vite en flèche en terme de temps d’exécution. Plus je vais dans les pages profondes également (avec le LIMIT)

J'ai également essayé de forcer des index ou du d'utiliser STRAIGHT JOIN, mais il est hors de question que je force mysql a faire d'une façon qui pour lui ne lui semble pas optimisé. Trop de risque qu'à un moment donné une requête me pète a la figure.

En gros comment faire lorsqu'on fait des jointures (inner, left, right) et qu'on fait appel a plusieurs tables dans le WHERE, ORDER BY etc. pour optimiser la requête et les indexes ?

Il y aurait bien la solution de dé-normaliser :

mettre le champ date_modif également dans table t_taxon_article, mais je trouve pas ça propre également. De plus dans mon cas je fais des tris sur plusieurs champs, et je me vois donc mal ajouter tous mes champs utilisés pour les tris également dans ma table t_taxon_article. :roll:


Ca fait des mois que je suis sur le même problème, a chaque fois je passe a autre chose parceque je ne trouve aps la solution. Mais il faudra bien que j'en trouve une satisfaisante si je ne veux aps finir un jour par faire cracher le serveur.

Et autre question :

A chaque fois que je démarre mon serveur mysql (Wampserver en local - pas essayé en prod), en début de journée, lorsque j’exécute pour la 1ere fois mes requêtes elles mettent parfois plus d'1s, par contre ensuite elles retombent a moins de 0,050 s

J'ai essayé de mettre sql_no_cache dans mes requêtes pour voir si c'était parce qu’elles étaient mises en cache mais ca n'a pas l'air d'être le cas.

Je pars du principe que si une requête peu mettre 1s pour s’exécuter (avant l'utilisation d'une quel conque cache mysql) c’est qu'elle n’est pas optimisée.
 
WRInaute occasionnel
Bonjour,

As-tu essayé de créer un index sur la table t_taxon_article comprenant les colonnes (id_taxon, id_article) dans cet ordre ?
La clause ORDER BY ne devrait être évaluée qu'après la jointure.

C'est souvent salutaire.

Ton index primaire sur t_taxon_article est dans cet ordre ( id_article, id_taxon ) donc inutilisable sur WHERE id_article = xx.

Dis moi ce que ça donne...

A+
François
 
WRInaute discret
Quel est le type des tables ? Des colonnes ?
id_taxon est un int ou un char ?

Essaie de mettre "ta.id_taxon=44" dans la clause ON

Sinon j'utiliserai plus cette manière d'écrire la requête :

SELECT a.id_article FROM t_article as a, t_taxon_article as ta
WHERE ta.id_taxon=44
AND ta.id_article=a.id_article
ORDER BY a.date_modif LIMIT 100,10

Christophe
 
WRInaute accro
Salut

J'ai bien les 2 indexes multiples suivant dans t_taxon_article :

(id_taxon, id_article)
et
(id_article, id_taxon)

d'ailleurs (id_taxon, id_article) c’est ma clé primaire :wink:

Donc aucun résultat concluant avec ces 2 indexes. C'est embêtant car pour le moment la seule solution que je vois c'est la dénormalisation en duplicant mes données de tris (date_modif etc.) présentent dans t_articles dans t_taxon_bloc.
c’est carrément pas propre.

Après moult essais avec des tables bien remplies les solutions de requêtes imbriquées, straight join, ou force index s'avèrent également très mauvaises. Et retirer mon order by n’est évidemment pas envisageable.

Je suis tombé sur de nombreux sujets avec la même problématique et je n'ai jamais trouvé de réponses concluantes :

http://stackoverflow.com/questions/13633406/using-index-using-temporar ... o-fix-this
http://stackoverflow.com/questions/23228966/fix-using-index-using-temp ... g-filesort
http://stackoverflow.com/questions/8673146/mysql-order-by-optimization ... iple-joins
http://forums.devshed.com/mysql-help-4/trying-understand-optimizing-us ... 06712.html
etc.

D'ailleurs j'ai télécharger wordpress pour regarder comment sont faites les requêtes (étant donné que je travail sur presque le même schéma au niveau de la gestion des taxons)

J'ai trouvé ceci sur wordpress :

Code:
SELECT SQL_CALC_FOUND_ROWS wp_posts.ID FROM wp_posts INNER JOIN wp_term_relationships ON (wp_posts.ID = wp_term_relationships.object_id) WHERE 1=1 AND ( wp_term_relationships.term_taxonomy_id IN (6) ) AND wp_posts.post_type = 'post' AND (wp_posts.post_status = 'publish' OR wp_posts.post_status = 'private') GROUP BY wp_posts.ID ORDER BY wp_posts.post_date DESC LIMIT 0, 10

on a bien dans la clause where et order by une utilisation de colonnes de tables différentes. D'ailleurs si je fait un explain de cette requête il y a également un use temporary, use filesort.
D'ailleurs a quoi sert le 1=1?

Ce qui sous entend que même sur wordpress ce n’est pas optimisé et qu'un site qui commence a avoir un très grand nombre d'articles (plusieurs dizaines de milliers risque d'exploser), J’imagine ensuite que wordpress règle en partie le probleme avec la cache. Mais la cache est-elle al seule solution?

Qui a un site réalisé via wordpress avec plus de 10 000 articles?
 
WRInaute accro
@chain35 : mes tables sont en Mysam, mes colonnes id_taxon et id_article sont des int(10)

j'ai essayé la requête indiquée ainsi que celle-ci :

Code:
 SELECT a.id_article FROM t_article as a
INNER JOIN t_taxon_article as ta ON (ta.id_article=a.id_article and ta.id_taxon=44)
ORDER BY a.date_modif  LIMIT 100,10

et j'ai strictement le même résultat d'ailleurs les 3 requêtes sont enfin de compte identiques pour mysql, c’est juste une façon d'écrire. :mrgreen:

j'ai également essayé de mettre left join (même si ce n'est pas ce que je veux) à la place de inner join, mais ça change rien

Vous avez bien vous aussi ce genre de requête? comment gérer vous les tris sur vos pages de categories, tags etc?

ce n'est quand même pas rare ce genre de requete :?
 
WRInaute discret
Essaie la requête sans order by pour voir si c'est le tri qui ralentit le tout.

Evite INT(10), c'est trop gros pour ton besoin.

Essaie aussi en ajoutant une clé que sur id_taxon.

Christophe
 
WRInaute occasionnel
re,

Un workaround que je viens de trouver : les colonnes utilisées dans le ORDER BY devraient se trouver dans le WHERE...

http://www.olivierpons.fr/2007/12/10/mysql-optimisation-mysql-sur-le-order-by/

essayes avec :

Code:
SELECT a.id_article FROM t_article as a
INNER JOIN t_taxon_article as ta ON (ta.id_article=a.id_article)
WHERE  ta.id_taxon=44 and a.date_modif <= NOW()
ORDER BY a.date_modif  LIMIT 100,10

As-tu posé la question sur developpez.com ?
 
WRInaute accro
@chnain35 : alors j'ai regardé un peu toutes mes clés, j'ai passé les id_taxon de int(10) a smallint(5) et les id_article a mediumint(8) au lieu de int(10)
j'ai également ajouté l'index id_taxon, le résultat est toujours identique.

Sans le order by la requête est quasi instantanée, le problème c’est que j'ai justement besoin de pouvoir effectuer ces tris. :wink:

@François : requête qui donne également le même résultat, j'avais meme essayé d'ajouter ceci : select a.id_article, a.date_modif :wink:

oui j'ai posé la question sur developpez.com et d'autres forums et j'ai obtenu des solutions du style :

- passer par une requête imbriquée
ou
- straight join
ou
- force index

Aucune de ces 3 solutions n’est bonne, car en fonction du nombre d'articles pour un taxon donné etc il y en a toujours une qui pète un plomb à un moment donnée. J'ai donc testé, en plus des jointures, ces 3 solutions en large et en travers et je n'ai obtenu aucun résultat satisfaisant :(

1) pour les jointures, le soucis vient du fait que l'on fait appel dans le clause et le order by a des colonnes de tables différentes, résultat l'optimiseur est obligé d'utiliser une table temporaire et de retrier

2) pour la requête imbriquée, si dans t_taxon il y a plus d'enregistrement que dans t_articles, lorsque peu d'articles sont associé a tel ou tel taxon la requête explose :)

3) straight join ou force index, on comprend assez vite le problème. Si l'optimiseur n'utilise pas tel ou tel index etc, ce n’est pas pour rien et on le vérifie très vite lors qu'on essaye d'aller contre ces choix. A un moment donnée idem notre requete explose.

La plus stable semble être avec les jointures, malheureusement cette solution reste ici très gourmande à cause de ces fichus use temporary, use filesort et qu'il ne peut pas utiliser les indexes

Donc je me demande bien comment font ceux qui trient d'une certaines façons leurs articles dans leurs catégories, tags etc.

par exemple, comment se présente vos tables pour gérer les tags, comme ceci ?

Code:
t_tags
id tag
tag

t_tag_article
id_article
id_tag

t_articles
id_article
titre
contenu
date_creation
date_modif
en_ligne
etc.

et comment feriez vous avec ces 3 tables pour afficher les 10 premiers articles ayant pour id_tag = X ordonné par date de modification?
peut être que c'est ma structure de stables qui n’est pas bonne? mais par rapport a l’exemple ci-dessus je ne vois vraiment pas quelle autre solution peut-il exister...
 
WRInaute occasionnel
ok,

Je te comprends, c'est ch***t quand on arrive pas à optimiser un requête... :(

Combien de lignes sont retournées sans la clause LIMIT? est-ce rapide sans cette clause?

As-tu essayer de jouer sur le sort_buffer?

Je te demande ça car le tri dépend du nb de lignes du resultset, cela ne va peut-être pas augmenter tant que ça... jusqu'à crasher ton serveur.

A+
 
WRInaute accro
Le nombre de ligne retourné sans limit est totalement aléatoire tout dépend du nombre d'articles dans le taxon demandé
étant donné qu'un taxon peut être aussi bien une catégorie, une sous catégorie, un tag etc. Un peu a la façon de Wordpress.
Dans ma base de donnée test, je peux avoir des catégories avec 10 000 articles, et des sous catégories avec 100 articles.

D'ailleurs avec les jointures, avec ou sans limit ça ne change rien, ça met autant de temps avec "limit 0,1" que sans limit. Tout simplement parce qu’il est obligé de créer une table temporaire et de trier tous les champs avant de me donner le résultat attendu.

Avec la solution de la requête imbriquée c’est plutôt efficace si je suis dans une catégorie avec beaucoup d'articles et que je suis dans les 1ere pages. Mais plus je vais dans les pages profonde et plus le temps d’exécution grimpe en flèche.
Et si j'ai peu d'article dans une catégorie ça prend de suite beaucoup de temps et c’est pire qu'avec les jointures.

Ca rend vraiment chèvre. Quand tu crois trouver la solution et la bonne requête tu te rend compte qu'elle est pire dans certain cas.

Je ne souhaite pas modifier les paramètres de mysql (sort_buffer etc.) dans la mesure ou je test en local et qu'en suite en production (sur un mutualisé) je ne pourrais rien changer.
Ce qui est déjà inquiétant c'est que c’est déjà lent en local (parfois plus d'1s pour afficher certaines catégories) alors je crains les temps d’exécution sur mutualisé (qui sont généralement bien plus lents).

Même si je ne fais pas crasher le serveur, si je met plus de 5s pour accéder à une catégorie ça va pas le faire :wink:

J'ai fait des tests avec environ 60 000 articles et plus de 200 000 enregistrements dans ma table t_taxon_article
Normalement je ne devrais pas dépasser ce chiffre avant longtemps, très longtemps, mais bon, vu mes temps d’exécution, avec 2 fois moins d'enregistrement ça serait encore trop long.

Mais le truc qui m'agace dans tout ça, c’est que je ne vois pas en quoi ce que je demande (ma requête), est si exceptionnel. quasiment tous les sites ont des catégories et des tags et affichent leurs articles dans un certain ordre (date de modif, votes, ou je ne sais quoi encore). Comment font-ils? Pourquoi ne sont-ils pas confronté au même problème :?

Celui qui me trouve la solution aura vraiment toute ma gratitude éternelle :mrgreen:
 
WRInaute occasionnel
Bonjour,

Je viens de faire un test sur une simple requête avec un tri descendant sur date sur 4000 lignes, sans jointures.
Résultat AVANT mise en cache, 89 ms pour 1000 lignes soit presque 1 sec pour 10000. Le test a été fait sur une base Wordpress, table des posts.

Donc, je pense qu'avec 10000 articles pour une catégorie tu ne feras pas mieux, car avec la clause LIMIT il faut lire toutes les lignes. Et si il n'y a pas de clause LIMIT, il faut aussi lire ces lignes.

J'ai bien peur qu'il faille sot accepter ce résultat soit changer les classements des catégories pour avoir moins d'articles dans chacune d'elles. :(

Bon weekend!

François
 
WRInaute accro
@chnain35 : oui j'ai bien un mediumint (8) sur les 2 tables. je fais bien attention que les jointures correspondent bien a des types et tailles identiques :wink:

@François : ah, donc même wordpress rencontre le même problème? donc dès qu'il y a plusieurs milliers d'articles dans une categorie sur wordpres, ça met 1s voir plus? (avant la mise en cache). D'ailleurs c’est 1s avant qu'elle mise en cache? est-ce que c'ets une cache générale également présente sur les mutualisé? ou une cache intégré sur wordpress?

En local sur mon site avec 10 000 articles dans une catégorie, je fais environ 0.8980 sec a la première exécution et 0.0429 sec. J’espère que sur un serveur mutualisé (généralement moins performant) la requête ne vas pas passer à plusieurs seconde et que la cache mysql est bien utilisé sur les mutualisés.

Pour 1400 articles je fais 0,33s (ça me parait élevè :/) à la première exécution et 0.008s ensuite.
En tout cas la requête avec jointure, malgré le use temporary et use filesort, semble être la requête la plus table par rapport aux autres solutions (requêtes imbriquées, force (use) index, straight join...).
Et j'en arrive vraiment à la conclusion que soit je laisse comme ca et j'essaye de limiter la casse avec la cache (cache php par exemple), soit je dé normalise en dupliquant mes champs utilisés pour les tris (date_modif etc.) dans ma table t_taxon_article :|
 
WRInaute accro
alors petite rectification, pour être certain de bien vider toutes cache de mysql, je redémarre mon pc.
Avec 10 000 articles dans la catégorie a, je suis a 2,5s a la première exécution
avec 1300 articles dans la catégorie b, je suis a 1,1s à la première exécution
:?
 
WRInaute impliqué
Avec cette requête, Using temporary; Using filesort sont remplacés par Using Where.
Ce qui semble correspondre à ta demande.
Code:
SELECT a.id_article
FROM t_article as a
LEFT JOIN t_taxon_article as ta ON (ta.id_article=a.id_article) and  ta.id_taxon=91
WHERE
a.en_ligne=1
ORDER BY a.date_modif desc
limit 0,20
Code:
id 	select_type 	table 	type 	possible_keys 	key 	key_len 	ref 	rows 	Extra
1	SIMPLE	a	ref	idx_ligne_membre,idx_tri	idx_tri	1	const	56999	Using where; Using index
1	SIMPLE	ta	eq_ref	PRIMARY,idx_art_tax	PRIMARY	5	const,test.a.id_article	1	Using index

Par contre, niveau temps d'exécution, ça ne change rien du tout.
Ça tourne autour de 0.000114 dans le fichier de requête lente.

Laquelle de ta requête te semble la plus lente ?
 
WRInaute accro
La requête n’est pas bonne ;)
Le left join récupère ici tous les articles, même si ils ne sont pas dans l'id_taxon 91.

Pour l'id_taxon 91 normalement il y a 17 000 articles, dans ta requêtes tu récupères les 57 000 (enlève le limit pour t'en rendre compte) :wink:
 
WRInaute impliqué
Désolé, c'est parce que je ne suis pas en mode "c'est qui fait le truc alors j'ai tout en tête" ^^

Je propose autre chose. Truc marrant, juste en ajoutant "ta.id_taxon IS NOT NULL" dans la clause WHERE, ça passe toujours sans "Using temporary; Using filesort" et ça permet d’éliminer les articles inutiles :
Code:
SELECT a.id_article
FROM t_article as a
LEFT JOIN t_taxon_article as ta ON (ta.id_article=a.id_article) and  ta.id_taxon=91
WHERE a.en_ligne=1 AND ta.id_taxon IS NOT NULL
ORDER BY a.date_modif desc
limit 0,20
Code:
id 	select_type 	table 	type 	possible_keys 	key 	key_len 	ref 	rows 	Extra
1	SIMPLE	a	ref	PRIMARY,idx_ligne_membre,idx_tri	idx_tri	1	const	24455	Using index
1	SIMPLE	ta	eq_ref	PRIMARY,idx_art_tax	PRIMARY	5	const,test.a.id_article	1	Using where; Using index

Avec cette requête, j'ai 17783 résultats. Je te laisse vérifier que c'est correct ou si j'ai encore fait une bourde ;)
 
WRInaute accro
ah mince, j'ai eu une lueur d'espoir, mais je comprend pas moi j'ai toujours le using temporary, using filesort :

Code:
id 	select_type 	table 	type 	possible_keys 	key 	key_len 	ref 	rows 	Extra 	
1 	SIMPLE 	ta 	ref 	PRIMARY,idx_art_tax 	PRIMARY 	2 	const 	18696 	Using where; Using index; Using temporary; Using filesort
1 	SIMPLE 	a 	eq_ref 	PRIMARY,idx_ligne_membre,idx_tri 	PRIMARY 	3 	test.ta.id_article 	1 	Using where

:(

j'obtiens la même chose qu'avec le inner join, dommage ça paraissait pas mal du tout, il prend pas idx_tri, et il ne commence pas par a

J'ai mysql 5.6.12 (en local), une version différente peur faire cette différence :/

tu n'as rien chnagé au niveau des index? C’est vraiment chaud de comprendre cette optimiseur, et si en plus son comportement change en fonction de l'ordi ca n'arrange pas les choses :?
 
WRInaute impliqué
Mince, en fait j'ai voulu faire un test en passant les tables en Innodb. J'ai bien réinitialisé les tables avec tes fichiers avant mes tests, sauf que j'ai oublié de remettre les bonnes valeurs dans tes fichiers mdr.

Ou comment découvrir des choses par hasard …

La modif à faire dans les fichiers SQL est de remplacer:
Code:
) ENGINE=MyISAM DEFAULT CHARSET=utf8 ROW_FORMAT=DYNAMIC;
Par:
Code:
) ENGINE=Innodb DEFAULT CHARSET=utf8;

D'ailleurs, pourquoi MyISAM ? Niveau intégrité des données, il me semble que Innodb est largement meilleur.
Autre chose, ça sert à quoi ROW_FORMAT=DYNAMIC ?

En tout cas, en passant en Innodb, ça semble passer correctement. Après un test avec MyISAM, j'ai effectivement les mêmes valeurs que toi.

Pour infos, j'ai fais un essai en supprimant juste ROW_FORMAT=DYNAMIC, et j'ai aussi les Using temporary; Using filesort.
 
WRInaute accro
intéressant, j'utilise myisam pour la recherche fulltext, et étant donné que je fais surtout du select ca semblait donc bien plus approprié

d'ailleurs pour que le fichier soit moins lourd j'avais retiré mon index fulltext (titre, contenu) :wink:

je ne sais pas du tout a quoi sert : ROW_FORMAT=DYNAMIC et pourquoi j'ai ça :mrgreen:
 
WRInaute accro
alors j'ai testé en innodb, et je dois dire que question embrouille c’est pas mal :)

Lorsque je teste avec l'id_taxon = 91 (qui contient 17000 articles) il n'y a effectivement plus aucun temporary et filesort
ça semble avoir le même comportement qu'avec l'utilisation de la requête imbriquée. Même en mettant inner join.

Avec l'id 44 (1200 articles) ou 197 (1 article) on a nouveau les using filesort et temporary.

difficile de savoir quand il va vraiment utiliser la table temporaire ou non
 
Discussions similaires
Haut