2016-04-12 9 views
1

Ich habe eine MySQL-Tabelle, die mit Mails aus einem Postfix-Mail-Protokoll gefüllt ist. Die Tabelle wird sehr oft mehrmals pro Sekunde aktualisiert. Hier ist die SHOW CREATE TABLE Ausgabe:Gibt es eine Möglichkeit, diese SELECT-Abfrage weiter zu optimieren?

Create Table postfix_mails CREATE TABLE `postfix_mails` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT, 
`mail_id` varchar(20) COLLATE utf8_danish_ci NOT NULL, 
`host` varchar(30) COLLATE utf8_danish_ci NOT NULL, 
`queued_at` datetime NOT NULL COMMENT 'When the message was received by the MTA', 
`attempt_at` datetime NOT NULL COMMENT 'When the MTA last attempted to relay the message', 
`attempts` smallint(5) unsigned NOT NULL, 
`from` varchar(254) COLLATE utf8_danish_ci DEFAULT NULL, 
`to` varchar(254) COLLATE utf8_danish_ci NOT NULL, 
`source_relay` varchar(100) COLLATE utf8_danish_ci DEFAULT NULL, 
`target_relay` varchar(100) COLLATE utf8_danish_ci DEFAULT NULL, 
`target_relay_status` enum('sent','deferred','bounced','expired') COLLATE utf8_danish_ci NOT NULL, 
`target_relay_comment` varchar(4098) COLLATE utf8_danish_ci NOT NULL, 
`dsn` varchar(10) COLLATE utf8_danish_ci NOT NULL, 
`size` int(11) unsigned NOT NULL, 
`delay` float unsigned NOT NULL, 
`delays` varchar(50) COLLATE utf8_danish_ci NOT NULL, 
`nrcpt` smallint(5) unsigned NOT NULL, 
PRIMARY KEY (`id`), 
UNIQUE KEY `mail_signature` (`host`,`mail_id`,`to`), 
KEY `from` (`from`), 
KEY `to` (`to`), 
KEY `source_relay` (`source_relay`), 
KEY `target_relay` (`target_relay`), 
KEY `target_relay_status` (`target_relay_status`), 
KEY `mail_id` (`mail_id`), 
KEY `last_attempt_at` (`attempt_at`), 
KEY `queued_at` (`queued_at`) 
) ENGINE=InnoDB AUTO_INCREMENT=111592 DEFAULT CHARSET=utf8 COLLATE=utf8_danish_ci 

Ich möchte wissen, wie viele E-Mails über einen bestimmten Host zu einem bestimmten Zeitpunkt weitergeleitet wurden, so dass ich mit dieser Abfrage:

SELECT COUNT(*) as `count` 
FROM `postfix_mails` 
WHERE `queued_at` LIKE '2016-04-11%' 
    AND `host` = 'mta03' 

Die Abfrage dauert zwischen 100 und 110 ms.

Derzeit enthält die Tabelle etwa 70 000 Mails, und die Abfrage gibt rund 31 000 zurück. Dies sind nur ein paar Tage im Wert von Mails, und ich plane, mindestens einen Monat zu behalten. Der Abfragecache hilft nicht viel, da die Tabelle ständig aktualisiert wird.

Ich habe versucht, dies zu tun, statt:

SELECT SQL_NO_CACHE COUNT(*) as `count` 
FROM `postfix_mails` 
WHERE `queued_at` >= '2016-04-11' 
    AND `queued_at` < '2016-04-12' 
    AND `host` = 'mta03' 

Aber die Abfrage dauert die exakt gleiche Zeit laufen zu lassen.

[mysqld] 
query_cache_size = 128M 
key_buffer_size = 256M 

read_buffer_size = 128M 
sort_buffer_size = 128M 

innodb_buffer_pool_size = 4096M 

Und bestätigt, dass sie alle in Kraft sind (SHOW VARIABLES), aber die Abfrage läuft nicht schneller: Ich habe diese Änderungen an der MySQL-Konfiguration vorgenommen.

Mache ich etwas dumm, dass diese Abfrage so lange dauern macht? Können Sie irgendwelche offensichtlichen oder nicht offensichtlichen Wege finden, um es schneller zu machen? Gibt es eine andere Datenbank-Engine, die in diesem Szenario besser funktioniert als InnoDB?


mysql> EXPLAIN SELECT SQL_NO_CACHE COUNT(*) as `count` 
    -> FROM `postfix_mails` 
    -> WHERE `queued_at` >= '2016-04-11' 
    -> AND `queued_at` < '2016-04-12' 
    -> AND `host` = 'mta03'; 
+----+-------------+---------------+------+--------------------------+----------------+---------+-------+-------+-------------+ 
| id | select_type | table   | type | possible_keys   | key   | key_len | ref | rows | Extra  | 
+----+-------------+---------------+------+--------------------------+----------------+---------+-------+-------+-------------+ 
| 1 | SIMPLE  | postfix_mails | ref | mail_signature,queued_at | mail_signature | 92  | const | 53244 | Using where | 
+----+-------------+---------------+------+--------------------------+----------------+---------+-------+-------+-------------+ 
1 row in set (0.00 sec) 
+1

Leistungsfragen sollten 'EXPLAIN ANALYSE' und einige Informationen über Tabellengröße, Index, aktuelle Zeitleistung, Wunschzeit usw. enthalten.' Langsam' ist ein relativer Begriff und wir brauchen einen echten Vergleichswert. [** MySQL **] (http://dba.stackexchange.com/questions/15371/how-do-i-get-the-execution-plan-for-a-view) Überprüfen Sie auch den MySQL-Index [* * TIPS **] (http://mysql.rjweb.org/doc.php/index_cookbook_mysql) –

+0

Bitte zeigen Sie uns die EXPLAIN SELECT SQL_NO_CACHE COUNT (*) als "count" VON 'postfix_mails' WHERE' queued_at'> = '2016-04-11' UND 'queued_at' <'2016-04-12' AND' host' = 'mta03'; –

+0

@JuanCarlosOropeza MySQL hat nicht 'EXPLAIN ANALYSE' AFAIK, aber ich habe die' EXPLAIN' Ausgabe hinzugefügt – Hubro

Antwort

1

Wenn Sie Ihre Abfrage wirklich schnell benötigen, müssen Sie materialisieren.

MySQL fehlt eine Möglichkeit, die nativ zu tun, so dass Sie eine Tabelle wie das erstellen müssen werden:

CREATE TABLE mails_host_day 
     (
     host VARCHAR(30) NOT NULL, 
     day DATE NOT NULL, 
     mails BIGINT NOT NULL, 
     PRIMARY KEY (host, day) 
     ) 

und aktualisieren, entweder in einem Trigger auf postfix_mails oder mit einem Skript ab und zu:

INSERT 
INTO mails_host_day (host, day, mails) 
SELECT host, CAST(queued_at AS DATE), COUNT(*) 
FROM postfix_mails 
WHERE id > :last_sync_id 
GROUP BY 
     host, CAST(queued_at AS DATE) 
ON DUPLICATE KEY 
UPDATE mails = mails + VALUES(mails) 

Auf diese Weise ist die Abfrage eines Host-Tag-Eintrags eine einzelne Primärschlüsselsuche.

Beachten Sie, dass die triggerbasierte Lösung die DML-Leistung beeinflusst, während die scriptbasierte Lösung zu etwas weniger tatsächlichen Daten führt.

Allerdings können Sie die Skript-basierte Lösung Aktualität verbessern, wenn Sie Vereinigung die jüngsten tatsächlichen Daten mit den gespeicherten Ergebnissen:

SELECT host, day, SUM(mails) AS mails 
FROM (
     SELECT host, day, mails 
     FROM mails_host_day 
     UNION ALL 
     SELECT host, CAST(queued_at) AS day, COUNT(*) AS mails 
     FROM postfix_mails 
     WHERE id >= :last_sync_id 
     GROUP BY 
       host, CAST(queued_at) AS day 
     ) q 

Es ist kein einziger Index mehr suchen, aber wenn Sie das Update ausführen Skript oft genug, wird es weniger tatsächliche Datensätze zu lesen.

+0

Ja das ist wahrscheinlich der Weg zu gehen ... – Hubro

0

Sie haben einen eindeutigen Schlüssel auf ‚Host‘, ‚mail_id‘ und ‚zu‘, aber wenn die Abfrage-Engine diesen Index zu verwenden versucht, Sie filtern nicht auf ‚mail_id‘ und "zu", also ist es vielleicht nicht so effizient. Eine Lösung könnte darin bestehen, einen weiteren Index nur auf 'host' hinzuzufügen oder AND 'mail_id' IS NOT NULL AND'to' IS NOT NULL zu Ihrer Abfrage hinzuzufügen, um den vorhandenen eindeutigen Index vollständig zu nutzen.

2

queued_at ist ein Datetime-Wert. Verwenden Sie nicht LIKE. Dadurch wird es in eine Zeichenfolge konvertiert, wodurch die Verwendung von Indizes verhindert und eine vollständige Tabellensuche erzwungen wird. Stattdessen möchten Sie einen geeigneten Index und die Abfrage reparieren.

Die Abfrage ist:

SELECT COUNT(*) as `count` 
FROM `postfix_mails` 
WHERE `queued_at` >= '2016-04-11' AND `queued_at` < DATE_ADD('2016-04-11', interval 1 day) AND 
     `host` = 'mta03'; 

Dann sind Sie auf postfix_mails(host, queued_at) einen zusammengesetzten Index möchten. Die Spalte host muss zuerst angezeigt werden.

Hinweis: Wenn Sie Ihre aktuelle Version ist 31.000 von 70.000 E-Mail zu zählen, dann wird ein Index keine große Hilfe für das sein. Dies wird jedoch den Code für die Zukunft skalierbarer machen.

+0

Seltsamerweise dauert die Abfrage genau die gleiche Zeit wie meine, die zwischen 100 und 110 ms liegt. Ich nehme an, dass Ihr schneller sein wird, wenn die Zeilenanzahl auf mehrere zehn Millionen ansteigt. Ich hatte gehofft, dass es einen Weg gibt, es in 10 ms oder weniger zu laufen. Ich habe mit viel komplizierteren Tabellen mit vielen Beziehungen und Joins und Millionen von Zeilen, die riesige Abfragen in wenigen ms ausgeführt haben, behandelt. Ich habe damals SQLite3 benutzt, nicht MySQL. – Hubro

+0

@Hubro fügen Sie einen Index hinzu, wie Gordon vorgeschlagen hat. Du scheinst keinen zu haben. –

+0

Überprüfen Sie die Summe (Host) und Summe (queueed_at) mit Ihren Bedingungen. setzen Sie die selektivere (weniger Summe) als die erste in den zusammengesetzten Index – ninjabber

0

könnten Sie verwenden Paginierung Abfragen in PHP zu beschleunigen, die in der Regel ist, wie ich etwas lösen, die eine große Menge an Daten enthalten - aber das hängt von Ihrer Tabelle Hierarchie.

Integrieren Sie Ihre LIMIT in der SQL-Abfrage.

PHP:

foreach ($db->Prepare("SELECT COUNT(*) as `count` 
FROM `postfix_mails` 
WHERE DATEDIFF(`queued_at`, '2016-04-11') = 0) 
AND mail_id < :limit "))->execute(array(':limit' => $_POST['limit'])) as $row) 
{ 
    // normal output 
} 

jQuery:

$(document).ready(function() { 
    var starting = 1; 
    $('#next').click(function() { 
     starting = starting + 10; 
     $.post('phpfilehere.php', { limit: starting }) 
      .done(function(data) { 
       $('#mail-output').innerHTML = data; 
      }); 
    ); 

); 

Hier wird jede Seite zeigt 10 E-Mails auf, natürlich können Sie dies ändern und ändern und sogar eine Suche hinzufügen, die Ich habe tatsächlich ein Objekt, das ich für alle meine Projekte verwende.

Ich dachte nur, ich würde die Idee teilen - es fügt auch Echtzeit-Datenfluss auf Ihrer Website hinzu.

Dies wurde mir von Facebook Scrolling Show mehr inspiriert - das ist wirklich nicht schwer, aber ist so ein guter Weg für die Abfrage einer Menge von Daten.

Verwandte Themen