La sous-requête MySQL ralentit considérablement, mais elles fonctionnent bien indépendamment

8

Requête 1:

select distinct email from mybigtable where account_id=345

prend 0,1 s

Requête 2:

Select count(*) as total from mybigtable where account_id=123 and email IN (<include all from above result>)

prend 0,2 s

Requête 3:

Select count(*) as total from mybigtable where account_id=123 and email IN (select distinct email from mybigtable where account_id=345)

prend 22 minutes et 90% son à l'état "préparation". Pourquoi cela prend-il autant de temps?

La table est innodb avec 3,2 mil lignes sur MySQL 5.0

Stewie
la source

Réponses:

8

Dans la requête 3, vous exécutez essentiellement une sous-requête pour chaque ligne de mybigtable contre elle-même.

Pour éviter cela, vous devez apporter deux modifications majeures:

CHANGEMENT MAJEUR # 1: Refactoriser la requête

Voici votre requête d'origine

Select count(*) as total from mybigtable
where account_id=123 and email IN
(select distinct email from mybigtable where account_id=345)

Tu pourrais essayer

select count(*) EmailCount from
(
    select tbl123.email from
    (select email from mybigtable where account_id=123) tbl123
    INNER JOIN
    (select distinct email from mybigtable where account_id=345) tbl345
    using (email)
) A;

ou peut-être le nombre par e-mail

select email,count(*) EmailCount from
(
    select tbl123.email from
    (select email from mybigtable where account_id=123) tbl123
    INNER JOIN
    (select distinct email from mybigtable where account_id=345) tbl345
    using (email)
) A group by email;

CHANGEMENT MAJEUR # 2: Indexation appropriée

Je pense que vous l'avez déjà depuis Query 1 et Query 2 fonctionnent rapidement. Assurez-vous d'avoir un index composé sur (account_id, email). Faites-en un SHOW CREATE TABLE mybigtable\Get assurez-vous d'en avoir un. Si vous ne l'avez pas ou si vous n'êtes pas sûr, créez quand même l'index:

ALTER TABLE mybigtable ADD INDEX account_id_email_ndx (account_id,email);

MISE À JOUR 2012-03-07 13:26 EST

Si vous voulez faire un NOT IN (), changez le INNER JOINen a LEFT JOINet vérifiez que le côté droit est NULL, comme ceci:

select count(*) EmailCount from
(
    select tbl123.email from
    (select email from mybigtable where account_id=123) tbl123
    LEFT JOIN
    (select distinct email from mybigtable where account_id=345) tbl345
    using (email)
    WHERE tbl345.email IS NULL
) A;

MISE À JOUR 2012-03-07 14:13 EST

Veuillez lire ces deux liens pour faire des JOIN

Voici une excellente vidéo YouTube où j'ai appris à refactoriser les requêtes et le livre sur lequel elles étaient basées

RolandoMySQLDBA
la source
9

Dans MySQL, les sous-sélections de la clause IN sont réexécutées pour chaque ligne de la requête externe, créant ainsi O (n ^ 2). L'histoire courte est, n'utilisez pas IN (SELECT).

Aaron Brown
la source
1
  1. Avez-vous un index sur account_id?

  2. Le deuxième problème peut être avec les sous-requêtes imbriquées qui ont des performances terribles dans 5.0.

  3. GROUP BY avec une clause having est plus rapide que DISTINCT.

  4. Qu'essayez-vous de faire qui pourrait être mieux fait par le biais de jointures en plus de l'élément n ° 3?

Stephen Senkomago Musoke
la source
1

Il y a beaucoup de traitement impliqué lors de la gestion d'une sous-requête IN () telle que la vôtre. Vous pouvez en lire plus ici .

Ma première suggestion serait d'essayer de réécrire la sous-requête dans un JOIN à la place. Quelque chose comme (non testé):

SELECT COUNT(*) AS total FROM mybigtable AS t1
 INNER JOIN 
   (SELECT DISTINCT email FROM mybigtable WHERE account_id=345) AS t2 
   ON t2.email=t1.email
WHERE account_id=123
Derek Downey
la source