2015-08-21 5 views
7

Ich muss einige Daten von MySQL DB mit PHP auswählen. Es kann innerhalb einer einzigen MySQL-Abfrage ausgeführt werden, die 5 Minuten dauert, um auf einem guten Server ausgeführt zu werden (mehrere JOINs in Tabellen mit mehr als 10 Millionen Zeilen).Langsame MySQL-Abfrage - Cache die Daten in einem PHP-Array?

Ich frage mich, ob es eine bessere Praxis ist, die Abfrage in PHP aufzuteilen und einige Schleifen anstelle von MySQL zu verwenden. Außerdem wäre es besser, alle E-Mails von einer Tabelle mit 150 000 Zeilen in einem Array abzufragen und dann das Array zu überprüfen, anstatt Tausende von MySQL-SELECTs auszuführen.

Hier ist die Abfrage:

SELECT count(contacted_emails.id), contacted_emails.email 
FROM contacted_emails 
LEFT OUTER JOIN blacklist ON contacted_emails.email = blacklist.email 
LEFT OUTER JOIN submission_authors ON contacted_emails.email = submission_authors.email 
LEFT OUTER JOIN users ON contacted_emails.email = users.email 
GROUP BY contacted_emails.email 
HAVING count(contacted_emails.id) > 3 

Die EXPLAIN kehrt: EXPLAIN

Die Indizes in den 4 Tabellen sind:

contacted_emails: id, blacklist_section_id, journal_id and mail 
blacklist: id, email and name 
submission_authors: id, hash_key and email 
users: id, email, firstname, lastname, editor_id, title_id, country_id, workplace_id 

jobtype_id

der Tabelle contacted_emails erstellt wie:

CREATE TABLE contacted_emails ( 
    id int(10) unsigned NOT NULL AUTO_INCREMENT, 
    email varchar(150) COLLATE utf8_unicode_ci NOT NULL, 
    contacted_at datetime NOT NULL, 
    created_at datetime NOT NULL, 
    blacklist_section_id int(11) unsigned NOT NULL, 
    journal_id int(10) DEFAULT NULL, 
    PRIMARY KEY (id), 
    KEY blacklist_section_id (blacklist_section_id), 
    KEY journal_id (journal_id), 
    KEY email (email)) 
ENGINE=InnoDB AUTO_INCREMENT=4491706 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci 
+4

Als allgemeine Regel gilt, dass SQL immer schneller ist als PHP. Wenn Ihre Abfrage 5 Minuten dauert, sogar mit Millionen von Datensätzen und mehreren Joins, wette ich, dass es irgendwo eine suboptimale Syntax oder einen fehlenden Index gibt. Sie sollten ein EXPLAIN ausführen, um den Ausführungsplan Ihrer Abfrage zur weiteren Optimierung zu überprüfen. –

+1

Sie sollten eine spezifischere Frage, die Ihre Abfrage und EXPLAIN-Ausgabe zeigt, erneut senden und sehen, ob jemand sie beheben kann. –

+0

@StevenMoseley, danke. Bitte beachten Sie die Abfrage in der bearbeiteten Frage. Die große Tabelle ist contact_emails mit 10 Millionen Zeilen. Ich muss wissen, welche E-Mail in den contact_mails und nicht in Benutzern und nicht in submission_authors ist, und wurde mehr als 3 mal kontaktiert. –

Antwort

0

gibt Ihrem Empfehlungen, ich war diese Lösung entschieden:

SELECT ce.email, ce.number_of_contacts 
FROM (
    SELECT email, COUNT(id) AS number_of_contacts 
    FROM contacted_emails 
    GROUP BY email 
    HAVING number_of_contacts > 3 
) AS ce 
NATURAL LEFT JOIN blacklist AS bl 
NATURAL LEFT JOIN submission_authors AS sa 
NATURAL LEFT JOIN users AS u 
WHERE bl.email IS NULL AND sa.email IS NULL AND u.email IS NULL 

Diese 10sec nimmt zu laufen, die im Moment in Ordnung ist. Sobald ich mehr Daten in der Datenbank habe, muss ich über eine andere Lösung nachdenken, wo ich eine temporäre Tabelle erstellen werde.

Also, zum Schluss, Laden einer ganzen Tabelle als PHP-Array ist nicht gut für die Leistung als mysql Abfragen.

+1

Haben Sie versucht, 'COUNT (id)' in 'COUNT (*)' zu ändern? Ich wäre interessiert zu wissen, ob es die Leistung erhöht. Da Sie die Zählung bereits durchgeführt haben, können Sie 'HAVING number_of_contacts> 3' in der Unterabfrage verwenden. – Arth

+0

@Arth hat die Änderung von 'COUNT (id)' zu 'COUNT (*)' keinen Einfluss auf die Leistung. Die Änderung von 'HAVING COUNT (id)> 3' zu' HAVING number_of_contacts> 3' verbesserte jedoch die Leistung (von 20sec auf 10sec). Ich habe die Antwort bearbeitet, vielen Dank. –

2

Ein paar Gedanken, in Bezug auf die Abfrage Sie es schneller finden können, wenn Sie

count(*) row_count 

und die Änderung HAVING zu

row_count > 3 

, da dies von der contacted_emails.email zufrieden sein Index ohne Zugriff auf die Zeile, um die contacted_emails.id zu erhalten. Da beide Felder NOT NULL sind und contacted_emails ist die Basistabelle sollte dies die gleiche Logik sein.

Da diese Abfrage nur länger wird, wenn Sie mehr Daten sammeln, würde ich eine Übersichtstabelle vorschlagen, in der Sie die Anzahl speichern (möglicherweise pro Zeiteinheit). Dies kann entweder periodisch mit einem Cronjob oder im laufenden Betrieb mit Triggern und/oder Anwendungslogik aktualisiert werden.

Wenn Sie eine Option pro Zeiteinheit für created_at verwenden und/oder die letzte Aktualisierung des Cron speichern, sollten Sie in der Lage sein, Live-Ergebnisse zu erhalten, indem Sie die neuesten Daten einziehen und anhängen.

Jede Cache-Lösung müsste sowieso angepasst werden, um den Betrieb aufrechtzuerhalten, und die vollständige Abfrage wird jedes Mal ausgeführt, wenn die Daten gelöscht/aktualisiert werden.

Wie in den Kommentaren vorgeschlagen, ist die Datenbank für die Aggregation großer Datenmengen gebaut. PHP ist nicht.

+0

Wenn Sie E-Mail rechnen mit HAVING, müssen Sie DISTINCT verwenden, die ziemlich langsam ist. – Mihai

+0

@Mihai Yep, nicht sicher, ob Sie DISTINCT ganz korrekt sind, aber ich falsch verstanden die Gruppierung, werde ich – Arth

2

Sie würden wahrscheinlich am besten mit einer Übersichtstabelle sein, die über Trigger bei jedem Einfügen in Ihre Kontaktliste aktualisiert wird. Diese Übersichtstabelle sollte die E-Mail-Adresse und eine Zählungsspalte enthalten. Jedes Einfügen in eine Kontakttabelle aktualisiert die Anzahl. Haben Sie einen Index für Ihre Zählungsspalte in der Übersichtstabelle. Dann kannst du direkt von THAT abfragen, den betreffenden E-Mail-Account haben, DANN beitreten, um den Rest von allen Details abzuholen.

+0

diesen Vorschlag übernehmen, die nicht eine vernünftige Lösung ist. Wenn wir jedes Mal, wenn wir Daten aggregieren mussten, sollten wir "Zähl" -Tabellen erstellen, unsere Jobs, wie Programmierer sie aussaugen würden. Die Anzahl würde nicht mehr synchron sein. Marketing würde entscheiden, dass sie Durchschnittswerte oder Zählungen pro Monat oder was auch immer wollen. Dann müßten wir die ganze gehackte Programmierung wiederholen. Aus diesem Grund gibt es SQL - um diese komplexen Aufgaben im laufenden Betrieb erledigen zu können, benötigen wir daher keine Listen mit aggregierten Zahlen. –

+1

@StevenMoseley, ich stimme respektvoll zu. In einigen Fällen, und es basiert auf dem Kontext der Websites in Frage ... oder sogar Data-Mining im Allgemeinen. Wenn Trigger eingerichtet werden, um alle Aggregate, Rollups usw. zu aktualisieren, wäre die Abfrage von dieser Basis schneller. Die Tabelle wird ONCE erstellt, und die Trigger in der OTHER-Tabelle würden das Einfügen/Aktualisieren für Sie übernehmen. Sobald die primären Kriterien festgelegt sind, gelangen die Details in die rohen Daten. – DRapp

3

Ihre Indizes sehen gut aus.

Die Performance-Probleme scheinen aus der Tatsache abfinden, dass Sie JOIN ing alle Zeilen sind, dann HAVING mit Filtern.

stattdessen Dies würde wahrscheinlich besser arbeiten:

SELECT * 
FROM (
    SELECT email, COUNT(id) AS number_of_contacts 
    FROM contacted_emails 
    GROUP BY email 
    HAVING COUNT(id) > 3 
) AS ce 
LEFT OUTER JOIN blacklist AS bl ON ce.email = bl.email 
LEFT OUTER JOIN submission_authors AS sa ON ce.email = sa.email 
LEFT OUTER JOIN users AS u ON ce.email = u.email 
/* EDIT: Exclude-join clause added based on comments below */ 
WHERE bl.email IS NULL 
    AND sa.email IS NULL 
    AND u.email IS NULL 

Hier sind Sie Ihren ersten GROUP ed Datensatz vor dem JOIN s zu begrenzen, die deutlich mehr optimal ist.

Obwohl der Kontext Ihrer ursprünglichen Abfrage angegeben, die LEFT OUTER JOIN Tabellen dom't überhaupt verwendet zu werden scheint, so dass die unten wahrscheinlich genau die gleichen Ergebnisse mit noch weniger Aufwand zurückkehren würde:

SELECT email, COUNT(id) AS number_of_contacts 
FROM contacted_emails 
GROUP BY email 
HAVING count(id) > 3 

Was genau ist der Punkt dieser JOIN Ed-Tabellen? Die LEFT JOIN verhindert, dass sie die Daten reduzieren, und Sie betrachten nur die aggregierten Daten von contacted_emails. Meinst du stattdessen INNER JOIN?


EDIT: Sie erwähnt, dass der Zweck der Joins ist, E-Mails in Ihren vorhandenen Tabellen auszuschließen. Ich habe meine erste Abfrage geändert, um eine ordnungsgemäße Ausschluss-Verknüpfung zu erstellen (dies war ein Fehler in Ihrem ursprünglich geposteten Code).

Hier ist eine andere mögliche Option, die für Sie gut durchführen kann:

SELECT 
FROM contacted_emails 
LEFT JOIN (
    SELECT email FROM blacklist 
    UNION ALL SELECT email FROM submission_authors 
    UNION ALL SELECT email FROM users 
) AS existing ON contacted_emails.email = existing.email 
WHERE existing.email IS NULL 
GROUP BY contacted_emails.email 
HAVING COUNT(id) > 3 

Was ich hier mache, um die vorhandenen E-Mails in einer Unterabfrage zu sammeln und dabei eine einzige an diesem abgeleitete Tabelle beitreten auszuschließen.

Eine weitere Möglichkeit, Sie versuchen, kann dies zum Ausdruck bringen als nicht-korrelierten Unterabfrage in der WHERE-Klausel ist:

SELECT 
FROM contacted_emails 
WHERE email NOT IN (
    SELECT email FROM blacklist 
    UNION ALL SELECT email FROM submission_authors 
    UNION ALL SELECT email FROM users 
) 
GROUP BY email 
HAVING COUNT(id) > 3 

Versuchen Sie sie alle und sehen, welche den besten Ausführungsplan in MySQL Nach

+0

Hallo Steven, danke für deine Antwort. Der "LEFT OUTER JOIN" wird verwendet, um E-Mails auszuschließen, die sich bereits in den Tabellen "USERS", "submission_authors" und "blacklist" befinden. Ich muss diese E-Mails ausschließen. –

+0

@ Miloš - In diesem Fall sollten Sie einen IS NULL-Filter zum Ausschließen verwenden. Bearbeiten meiner Antwort. –

Verwandte Themen