2009-03-20 10 views
2

Cette requête MySQL fonctionne très bienMySQL Syntaxe et « OR » Performance

SELECT o.id 
FROM descriptions_programs d, titles_programs t, programs o 
WHERE (d.object_id=o.id 
     AND MATCH (d.text) AGAINST ('+china' IN BOOLEAN MODE) 
     AND d.current=1) 
AND (t.object_id=o.id 
     AND MATCH (t.text) AGAINST ('+china' IN BOOLEAN MODE) 
     AND t.current=1) 

Mais si je remplace un ET avec un OU, la requête est exécutée très longtemps. (Je dois le tuer.):

SELECT o.id 
FROM descriptions_programs d, titles_programs t, programs o 
WHERE (d.object_id=o.id 
     AND MATCH (d.text) AGAINST ('+china' IN BOOLEAN MODE) 
     AND d.current=1) 
OR (t.object_id=o.id 
     AND MATCH (t.text) AGAINST ('+china' IN BOOLEAN MODE) 
     AND t.current=1) 

Pourquoi est-ce? Ne soyez pas accroché à la simplicité de + Chine. Je viens de simplifier cela pour le débogage. De plus, si je cours avec juste un des tests MATCH AGAINST, cela fonctionne bien, donc les deux sont corrects par eux-mêmes. J'ai l'impression que je provoque involontairement une énorme adhésion en utilisant OR, mais je ne comprends tout simplement pas. J'utilisais auparavant un test IN sur une UNION de deux sous-sélections et cela fonctionnait aussi. Droite?

Mise à jour: par demande de bobince. Ce n'est pas super lent, mais à ~ 500ms, ce n'est pas aussi rapide que d'utiliser UNION comme discussed here.

mysql> explain SELECT o.id 
    -> FROM programs o 
    -> JOIN titles_programs t ON t.object_id=o.id 
    -> JOIN descriptions_programs d ON d.object_id=o.id 
    -> WHERE MATCH (d.text) AGAINST ('+china' IN BOOLEAN MODE) AND d.current=1 
    -> OR MATCH (t.text) AGAINST ('+china' IN BOOLEAN MODE) AND t.current=1 
    -> ; 
+----+-------------+-------+-------+ 

----------------+----------------+---------+----------------------+--------+-------------+ 
| id | select_type | table | type | possible_keys | key   | key_len | ref     | rows | Extra  | 
+----+-------------+-------+-------+----------------+----------------+---------+----------------------+--------+-------------+ 
| 1 | SIMPLE  | o  | index | PRIMARY  | PRIMARY  | 4  | NULL     | 148666 | Using index | 
| 1 | SIMPLE  | d  | ref | object_current | object_current | 4  | haystack.o.id  |  1 |    | 
| 1 | SIMPLE  | t  | ref | object_current | object_current | 4  | haystack.d.object_id |  1 | Using where | 
+----+-------------+-------+-------+----------------+----------------+---------+----------------------+--------+-------------+ 

Répondre

1

Votre problème est que les jointures entre o et d et t doivent se produire dans tous les cas. Autrement dit, vous avez besoin de:

SELECT o.id 
FROM descriptions_programs d, titles_programs t, programs o 
WHERE d.object_id=o.id AND t.object_id=o.id AND 
(
     MATCH (d.text) AGAINST ('+china' IN BOOLEAN MODE) 
     AND d.current=1 
) OR ( MATCH (t.text) AGAINST ('+china' IN BOOLEAN MODE) 
     AND t.current=1 
) 

Pourquoi? Parce que dans votre première requête vous pouvez ignorer ces parenthèses - tout est AND ensemble et les tables se rejoignent bien. Dans votre deuxième requête, ce n'est pas vrai. Considérons ce que la base de données est vraiment en train de faire: Elle prend «toutes les lignes dans t» et croise cela avec «toutes les lignes dans d», par conséquent t*d lignes. Normalement, vous utilisez des jointures (comme je l'ai fait) pour limiter cela à une liste linéaire de lignes valides.

Mais dans votre requête OR vous permettent soit ligne pour correspondre o au lieu de deux correspondant o, donc pour chaque rangée dans une table qui correspond à vous choisissez également toutes les lignes de l'autre table.

+0

Toujours pas de différence. Fonctionne pendant> 1 minute avant que je doive le tuer. Changez le OU en ET, et il s'exécute en millisecondes. Même si vous supprimez l'une des deux phrases MATCH AGAINST. –

+0

Essayez d'exécuter uniquement la table 't'. Éliminer 'd' à la fois les clauses "FROM" et "WHERE". Alors quel est le temps de course? Si la réponse est «ça prend une éternité», alors ce n'est pas une question de «OU», c'est juste que la numérisation des tables est vraiment lente. –

+0

Vous devez faire attention à ce que "OU" vous changez pour un "ET": ou vous obtenez le comportement t * d. Aussi, vous devriez mettre des parenthèses en cas d'ambiguïté: je ne sais pas ce que la convention MySQL est, mais si vous dites "foo et bar ou voiture" alors cela pourrait signifier "(foo et bar) ou voiture" ce qui est mauvais! –

2

La réponse de Jason est sur place. De plus, je vais essayer d'utiliser la norme ANSI plus moderne syntaxe de jointure pour prendre la charge de la clause WHERE afin de soulager la confusion là:

SELECT o.id 
FROM programs o 
JOIN titles_programs t ON t.object_id=o.id 
JOIN descriptions_programs d ON d.object_id=o.id 
WHERE MATCH (d.text) AGAINST ('+china' IN BOOLEAN MODE) AND d.current=1 
OR MATCH (t.text) AGAINST ('+china' IN BOOLEAN MODE) AND t.current=1 

Cela arrêtera la croix-join provoquant une explosion inadvertant combinatoires; Je m'attendrais à ce qu'il fonctionne dans un délai raisonnable, à moins que la base de données soit vraiment énorme.

Si non, pouvez-vous publier les résultats d'un SELECT EXPLAIN de ce qui précède? On suppose que l'un ou les deux index de texte intégral ne sont pas utilisés. Je pourrais certainement imaginer que l'optimiseur de requêtes n'utilise pas le deuxième index fulltext, en faisant quelque chose comme essayer de "remplir" les lignes qui ne correspondent pas à la première requête fulltext au lieu d'aller directement à l'index.

Normalement, lorsque vous souhaitez combiner l'index de texte intégral sur deux colonnes, vous créez un index sur les deux colonnes. Ce serait en tout cas beaucoup plus rapide. Cependant, cela signifie que vous devez mettre des titres et des descriptions dans le même tableau. Cela peut ne pas être si difficile: puisque fulltext ne fonctionne que sur les tables MyISAM (et vous ne voulez généralement pas vos données canoniques dans les tables MyISAM), vous pouvez conserver la copie définitive de vos données dans des tables InnoDB correctement normalisées, avec une table MyISAM supplémentaire ne contenant que des appâts de recherche dénudés et tiges.

Si rien de tout cela n'est bon ... eh bien, je pense que je reviendrai à l'UNIONing que vous avez mentionné, couplé avec un filtre au niveau de l'application pour supprimer les ID en double.