Die MySQL-Unterabfrage verlangsamt sich drastisch, funktioniert jedoch unabhängig voneinander einwandfrei

8

Abfrage 1:

select distinct email from mybigtable where account_id=345

dauert 0,1s

Abfrage 2:

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

dauert 0,2s

Abfrage 3:

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

dauert 22 Minuten und 90% ist im "vorbereitenden" Zustand. Warum dauert das so lange?

Tabelle ist innodb mit 3,2mil Zeilen unter MySQL 5.0

Stewie
quelle

Antworten:

8

In Abfrage 3 führen Sie grundsätzlich eine Unterabfrage für jede Zeile von mybigtable gegen sich selbst aus.

Um dies zu vermeiden, müssen Sie zwei wichtige Änderungen vornehmen:

WICHTIGE ÄNDERUNG 1: Refaktorieren Sie die Abfrage

Hier ist Ihre ursprüngliche Anfrage

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

Du könntest es versuchen

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;

oder vielleicht die Anzahl pro 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;

WICHTIGE ÄNDERUNG 2: Richtige Indizierung

Ich denke, Sie haben dies bereits, da Abfrage 1 und Abfrage 2 schnell ausgeführt werden. Stellen Sie sicher, dass Sie einen zusammengesetzten Index für (account_id, email) haben. Tun Sie SHOW CREATE TABLE mybigtable\Gund stellen Sie sicher, dass Sie eine haben. Wenn Sie es nicht haben oder nicht sicher sind, erstellen Sie den Index trotzdem:

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

UPDATE 2012-03-07 13:26 EST

Wenn Sie ein NOT IN () ausführen möchten, ändern Sie das INNER JOINin a LEFT JOINund überprüfen Sie, ob die rechte Seite NULL ist, wie folgt:

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;

UPDATE 2012-03-07 14:13 EST

Bitte lesen Sie diese beiden Links, um JOINs zu machen

Hier ist ein großartiges YouTube-Video, in dem ich gelernt habe, Abfragen und das Buch, auf dem es basiert, umzugestalten

RolandoMySQLDBA
quelle
9

In MySQL werden Unterauswahlen innerhalb der IN-Klausel für jede Zeile in der äußeren Abfrage erneut ausgeführt, wodurch O (n ^ 2) erstellt wird. Die Kurzgeschichte ist, verwenden Sie nicht IN (SELECT).

Aaron Brown
quelle
1
  1. Haben Sie einen Index für account_id?

  2. Das zweite Problem kann bei den verschachtelten Unterabfragen liegen, die in 5.0 eine schreckliche Leistung aufweisen.

  3. GROUP BY mit einer have-Klausel ist schneller als DISTINCT.

  4. Was versuchst du zu tun, was möglicherweise besser durch Joins zusätzlich zu Punkt 3 getan werden kann?

Stephen Senkomago Musoke
quelle
1

Bei der Verarbeitung einer IN () - Unterabfrage wie Ihrer ist viel Verarbeitung erforderlich. Sie können mehr darüber lesen Sie hier .

Mein erster Vorschlag wäre, stattdessen zu versuchen, die Unterabfrage in einen JOIN neu zu schreiben. So etwas wie (nicht getestet):

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
quelle