2016-05-23 9 views
1

Ich versuche, aggregierte Ergebnisse (insgesamt eindeutige IPs) aus einer Tabelle mit etwa 2 Millionen neuen Zeilen jeden Tag zu erhalten.Optimierung der SELECT-Anzahl (DISTINCT IP)

Die Tabelle:

CREATE TABLE `clicks` (
    `id` int(10) unsigned NOT NULL AUTO_INCREMENT, 
    `hash` varchar(255) COLLATE utf8_unicode_ci NOT NULL, 
    `type` enum('popunder','gallery','exit','direct') COLLATE utf8_unicode_ci NOT NULL, 
    `impression_time` varchar(255) COLLATE utf8_unicode_ci NOT NULL, 
    `source_user_id` int(11) NOT NULL, 
    `destination_user_id` int(11) NOT NULL, 
    `destination_campaign_id` int(11) NOT NULL, 
    `destination_campaign_name` varchar(255) COLLATE utf8_unicode_ci NOT NULL, 
    `destination_campaign_url` varchar(255) COLLATE utf8_unicode_ci NOT NULL, 
    `ip` varchar(255) COLLATE utf8_unicode_ci NOT NULL, 
    `referrer` varchar(255) COLLATE utf8_unicode_ci NOT NULL, 
    `country_code` varchar(255) COLLATE utf8_unicode_ci NOT NULL, 
    `country_id` varchar(255) COLLATE utf8_unicode_ci NOT NULL, 
    `country` varchar(255) COLLATE utf8_unicode_ci NOT NULL, 
    `isp` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL, 
    `category_id` varchar(255) COLLATE utf8_unicode_ci NOT NULL, 
    `category` varchar(255) COLLATE utf8_unicode_ci NOT NULL, 
    `bid` float(8,2) NOT NULL, 
    `created_at` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00', 
    `updated_at` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00', 
    PRIMARY KEY (`id`), 
    KEY `ip` (`ip`), 
    KEY `source_user_id` (`source_user_id`), 
    KEY `destination_user_id` (`destination_user_id`), 
    KEY `destination_campaign_id` (`destination_campaign_id`), 
    KEY `clicks_hash_index` (`hash`), 
    KEY `clicks_created_at_index` (`created_at`), 
    KEY `campaign_date` (`destination_campaign_id`,`created_at`), 
    KEY `source_user_date` (`source_user_id`,`created_at`) 
) ENGINE=InnoDB AUTO_INCREMENT=301539660 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci; 

Meine Frage:

SELECT SUM(ips_by_date.count) as count, ips_by_date.date as date 
FROM (SELECT count(DISTINCT ip) as count, DATE(created_at) as date 
     FROM clicks as clicks 
     WHERE created_at BETWEEN '2016-05-22 00:00:00' AND '2016-05-23 23:59:59' 
     GROUP BY DATE(created_at)) as ips_by_date 
GROUP BY date; 

Nun nahm diese Abfrage 93 Sekunden für einen Tag zu laufen und ich fühle mich wie ich etwas fehle.

Gibt es irgendeine Optimierung, die ich machen kann, um die Leistung dieser einfachen Zählung zu beschleunigen?

Vielen Dank.

Antwort

2

Zunächst sehe ich nicht, warum eine Unterabfrage notwendig ist. Die innere Abfrage hat eine Zeile pro Datum. Es besteht keine Notwendigkeit, erneut zu aggregieren. Zweitens ist Ihre Abfrage für zwei Tage, aber ich bekomme die Punkte über die Leistung. So

, lassen Sie uns beginnen mit:

SELECT count(DISTINCT ip) as count, DATE(created_at) as date 
FROM clicks 
WHERE created_at BETWEEN '2016-05-22 00:00:00' AND '2016-05-23 23:59:59' 
GROUP BY DATE(created_at); 

Für diese Abfrage Sie auf clicks(created_at, ip) einen Index möchten. Beachten Sie auch, dass ich schreibe dies als:

SELECT count(DISTINCT ip) as count, DATE(created_at) as date 
FROM clicks 
WHERE created_at >= '2016-05-22' AND created_at < '2016-05-24' 
GROUP BY DATE(created_at); 

Dies sollte eine gewisse Verbesserung zeigen, aber ich glaube nicht, es radikal besser sein wird, da eine Datei Art für die äußere Aggregation noch notwendig ist.

+0

Ich habe Ihren vorgeschlagenen Index auf einer Teilmenge der heutigen Daten versucht, aber es ist zwar eine Verbesserung nicht viel. Was passiert, wenn ich diese eindeutige IP-Zählung nicht brauchen 100% genau zu sein? ist dies die Lösung ändern? ich weiß, dass in Elasticsearch können Sie die Leistung verbessern, wenn Sie die Genauigkeit handeln. Does etwas ähnliches in MySQL? Vielen Dank für Ihre Zeit. – user1782560

+0

@ user1782560. Nicht zu meinem Wissen. Google BigQuery hat eine ungefähre Anzahl unterscheiden. Wie viele Zeilen gibt es? –

0

Die Leistung hier läuft auf die Effizienz Ihrer Indizes hinaus, da es nicht viel Platz für Änderungen in Ihrem Code gibt (siehe Gordons Code für eine sauberere Version Ihres Codes).

Ein Index für (created_at) oder (created_at, ip) werden Sie unfortunatley nicht direkt distinct ip geben, ohne weitere Sortieranlage (da Sie von created_at nicht Gruppe tun), aber letztere zumindest würde erfordern keinen direkten Zugriff Tabelle. Die nächste Optimierung würde also einen Index auf (date(created_at), ip) erfordern, obwohl dies eine gewisse Duplizierung der Daten bedeuten würde.

Ab MySQL 5.7.6 können Sie eine generierte Spalte verwenden, um eine Spalte dt as date(created_at), vor 5.7.6 zu erstellen, erstellen Sie einfach eine Spalte dt und es manuell aktualisieren (wenn Sie jemals Ihre create_at -Wertes ändern, müssen Sie um einen Trigger hinzuzufügen, um diese Spalte entsprechend zu aktualisieren). Ihr erstes Update kann eine Weile dauern, also aktualisieren Sie es in Stapeln oder denken Sie darüber nach, es nur für zukünftige Abfragen zu verwenden.

einen Index (dt, ip) sollte nun Hinzufügen geben Sie das Ergebnis mit einem einzigen Index/Bereich Scan und ohne filesort und ohne die Notwendigkeit, date() von Datetime zu berechnen:

select count(distinct ip) as count, dt 
from clicks 
where dt >= '2016-05-22' and dt < '2016-05-24' 
group by dt; 

Wenn alles gut funktioniert, sollte dies nehmen Sie nur einige Sekunden für einige Millionen Zeilen.

Einige Dinge, die Sie immer noch Probleme verursachen können: Da 90 Sekunden immer noch eine relativ große Zahl für 2 Millionen Zeilen ist, könnte es bedeuten, dass Sie Probleme mit den Puffergrößen/RAM/HDD haben. Wenn es Sie z.B. 80 Sekunden, um den Index neu zu puffern und in den Speicher zu laden, gibt es danach nicht viel mehr. Ein einfacher Test dafür: Führen Sie Ihre Abfrage zweimal aus.Wenn es beim zweiten Mal (wirklich) deutlich schneller ist (zB < < 1/10), dann müssen Sie vielleicht daran denken, Ihre Systemeinstellungen, Architektur oder Partitionierung zu optimieren. Abgesehen davon sollten Sie Ihr System nicht optimieren (und manchmal nicht einmal einen weiteren Index oder eine Datumsspalte hinzufügen) und vielleicht andere, wichtigere Dinge verlangsamen - um tägliche Statistiken zu erhalten, könnten Sie genauso einfach eine Aufgabe ausführen um Mitternacht für alle Statistiken, die Sie denken und speichern Sie die Ergebnisse für Sie am Morgen schön und einfach zu sehen, es wäre egal, ob es Stunden dauert für Ihre Abfrage ausgeführt werden.

+0

'INDEX (Datum (created_at), IP) 'würde nicht helfen, Sie müssen immer noch die gleiche Anzahl von Zeilen lesen, a nd _that_ ist die tatsächliche Kosten in der Abfrage. –

0

Fügen Sie zuerst den bereits erwähnten zusammengesetzten Index hinzu. Dann wird das wirkliche Leistungsproblem eine Zillion Zeilen lesen, um eine COUNT(DISTINCT...) zu berechnen. Diese Aktion erfordert entweder das Sammeln aller Werte, das Sortieren und Ausführen einer GROUP BY oder der Versuch, alle eindeutigen Werte im RAM zu behalten.

Zusammenfassung Tabellen sind wunderbar für die Beschleunigung SUM, COUNT und sogar AVG in Data Warehousing-Anwendungen. Aber COUNT(DISTINCT...) (aka "Anzahl Unique User") eignet sich nicht für Übersichtstabellen. Wenn Sie bereit sind, einen kleinen Fehler zu akzeptieren, gibt es einen Weg. Siehe my blog.

Sie können es nicht erkennen, aber die Decke Verwendung von 255 in VARCHARmanchmal verursacht unnötige Leistungsprobleme. In diesem Fall haben Sie ip 765 Bytes in einer beliebigen TMP-Tabelle, vielleicht in der fraglichen Abfrage. Würde man es auf VARCHAR(39) CHARACTER SET ascii ändern, würde das um den Faktor 20 zurückgehen! (Es ist schwer vorauszusagen, wie viel, wenn überhaupt, Ihre Anfrage beschleunigt. Sie könnten es mit einer einfachen gespeicherten Funktion auf BINARY(16) runterladen.

+0

Die Lösung auf Ihrem Blog ist sehr nah an dem, was ich suche. 1% -2% Fehlerwahrscheinlichkeit klingt gut für mich. Aber es fällt mir schwer, das Konzept in eine MySQL-Abfrage umzuwandeln. Hast du einige Beispiele, wo du das implementiert hast? Auch wenn es nur das einfachste Beispiel ist, würde es sehr hilfreich sein. Vielen Dank! – user1782560