159 votes

MySQL - SELECT WHERE field IN (subquery) - Extrêmement lent pourquoi?

J'ai quelques doublons dans une base de données que je veux inspecter, donc ce que j'ai fait pour voir les doublons, c'est ceci:

SELECT relevant_field
FROM some_table
GROUP BY relevant_field
HAVING COUNT(*) > 1

De cette façon, j'obtiendrai toutes les lignes avec relevant_field apparaissant plus d'une fois. Cette requête prend des millisecondes à s'exécuter.

Maintenant, je voulais inspecter chacun des doublons, donc j'ai pensé que je pourrais SÉLECTIONNER chaque ligne dans some_table avec un relevant_field dans la requête ci-dessus, donc j'ai fait comme ceci:

SELECT *
FROM some_table 
WHERE relevant_field IN
(
    SELECT relevant_field
    FROM some_table
    GROUP BY relevant_field
    HAVING COUNT(*) > 1
)

Cela s'avère être extrêmement lent pour une raison quelconque (cela prend des minutes). Qu'est-ce qui se passe exactement ici pour que cela soit aussi lent? relevant_field est indexé.

Finalement, j'ai essayé de créer une vue "temp_view" à partir de la première requête (SELECT relevant_field FROM some_table GROUP BY relevant_field HAVING COUNT(*) > 1), et ensuite en faisant ma deuxième requête de cette façon à la place:

SELECT *
FROM some_table
WHERE relevant_field IN
(
    SELECT relevant_field
    FROM temp_view
)

Et ça marche très bien. MySQL le fait en quelques millisecondes.

Des experts en SQL ici qui peuvent expliquer ce qui se passe?

0 votes

Qu'est-ce que vous voulez exactement ? Voulez-vous supprimer les entrées en double sauf une ? Suggestion : veuillez lire Jointure auto

1 votes

Il est évident que le group-by est lent...

0 votes

La première requête s'exécute en millisecondes (celle qui regroupe et filtre avec HAVING). Ce n'est que combinée avec l'autre requête que tout devient lent (cela prend des minutes).

141voto

quano Points 5084

La sous-requête est exécutée pour chaque ligne car il s'agit d'une requête corrélée. On peut transformer une requête corrélée en une requête non corrélée en sélectionnant tout de la sous-requête, comme ceci :

SELECT * FROM
(
    SELECT relevant_field
    FROM some_table
    GROUP BY relevant_field
    HAVING COUNT(*) > 1
) AS subquery

La requête finale ressemblerait à ceci :

SELECT *
FROM some_table
WHERE relevant_field IN
(
    SELECT * FROM
    (
        SELECT relevant_field
        FROM some_table
        GROUP BY relevant_field
        HAVING COUNT(*) > 1
    ) AS subquery
)

4 votes

Cela a très bien fonctionné pour moi. J'avais une autre IN (sous-requête) dans une IN(sous-requête), et cela prenait plus de 10 minutes, tellement longtemps que j'ai googlé pendant que j'attendais. Envelopper chaque sous-requête dans SELECT * FROM () comme vous l'avez suggéré l'a réduit à 2 secondes !

0 votes

MERCI, j'ai essayé de trouver une bonne solution à cela pendant quelques heures maintenant. Cela a parfaitement fonctionné. J'aimerais pouvoir vous donner plus de upvotes! Cela devrait définitivement être la réponse.

0 votes

Fonctionne parfaitement. Une requête qui prenait ~50 secondes à s'exécuter est maintenant instantanée. J'aurais aimé pouvoir voter plus. Parfois, vous ne pouvez pas utiliser de jointures, c'est la bonne réponse.

115voto

Johan Points 34755

Réécrivez la requête comme suit

SELECT st1.*, st2.relevant_field FROM sometable st1
INNER JOIN sometable st2 ON (st1.relevant_field = st2.relevant_field)
GROUP BY st1.id  /* list a unique sometable field here*/
HAVING COUNT(*) > 1

_Je pense que st2.relevant_field doit être dans la sélection, sinon la clause having va générer une erreur, mais je ne suis pas sûr à 100%_

Ne jamais utiliser IN avec une sous-requête ; c'est notoirement lent.
Utilisez toujours IN avec une liste fixe de valeurs.

Plus de conseils

  1. Si vous voulez rendre vos requêtes plus rapides, ne faites pas un SELECT * sélectionnez seulement les champs dont vous avez vraiment besoin.
  2. Assurez-vous d'avoir un index sur relevant_field pour accélérer la jointure.
  3. Assurez-vous de faire un group by sur la clé primaire.
  4. Si vous êtes sur InnoDB et que vous ne sélectionnez que des champs indexés (et que les choses ne sont pas trop complexes) alors MySQL résoudra votre requête en utilisant uniquement les indexes, accélérant considérablement les choses.

Solution générale pour 90% de vos requêtes IN (select

Utilisez ce code

SELECT * FROM sometable a WHERE EXISTS (
  SELECT 1 FROM sometable b
  WHERE a.relevant_field = b.relevant_field
  GROUP BY b.relevant_field
  HAVING count(*) > 1)

1 votes

Vous pouvez également écrire cela avec HAVING COUNT(*) > 1. C'est généralement plus rapide en MySQL.

0 votes

@ypercube, fait pour la requête du bas, je pense que pour la requête du haut cela modifiera le résultat.

0 votes

@Johan : Étant donné que st2.relevant_field n'est pas NULL (il est déjà inclus dans la clause ON), cela n'altérera pas le résultat.

7voto

ceteras Points 1732
SÉLECTIONNER st1.*
DEPUIS some_table st1
inner join 
(
    SÉLECTIONNER relevant_field
    DEPUIS some_table
    GROUP BY relevant_field
    AYANT COUNT(*) > 1
)st2 ON st2.relevant_field = st1.relevant_field;

J'ai essayé votre requête sur l'une de mes bases de données, et j'ai également essayé de la réécrire comme une jointure à une sous-requête.

Cela a fonctionné beaucoup plus rapidement, essayez-le !

0 votes

Oui, cela créera probablement une table temporaire avec les résultats du groupe, donc cela sera aussi rapide que la version vue. Mais les plans de requête devraient dire la vérité.

6voto

0 votes

J'ai suspecté quelque chose comme ça, que la sous-requête est exécutée pour chaque ligne.

0 votes

Quelques versions de MySQL n'utilisent même pas un Index dans IN. J'ai ajouté un autre lien.

1 votes

MySQL 6 n'est pas encore stable, je ne le recommanderais pas pour la production!

3voto

user2244323 Points 11

Essayez ceci

SELECT t1.*
FROM 
 some_table t1,
  (SELECT relevant_field
  FROM some_table
  GROUP BY relevant_field
  HAVING COUNT (*) > 1) t2
WHERE
 t1.relevant_field = t2.relevant_field;

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X