Ich habe eine Abfrage, die zum Abrufen von Internet-Verkehrsstatistiken bestimmter IP-Adressen verwendet wird.
Es gibt separate IP-Adressfelder für hosts
und IP- Blocks, die aufgerufen werden assignments
. Die Daten werden in 5-Minuten-Intervallen gespeichert.
Die Abfrageergebnisse werden in der Zeitspalte gruppiert, und die Gesamtzahl der SUMs in und aus diesen 5-Minuten-Intervallen wird zum Zeichnen eines Diagramms verwendet.
Die Tabelle wird aufgerufen traffic
und enthält (Ende des Monats) rund 21 Millionen Datensätze.
SHOW CREATE table traffic:
CREATE TABLE `traffic` (
`type` enum('v4_assignment','v4_host','v6_subnet','v6_assignment','v6_host') NOT NULL,
`type_id` int(11) unsigned NOT NULL,
`time` int(32) unsigned NOT NULL,
`bytesin` bigint(20) unsigned NOT NULL default '0',
`bytesout` bigint(20) unsigned NOT NULL default '0',
KEY `basic_select` (`type_id`,`time`,`type`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1
SELECT traffic.time, SUM(traffic.bytesin), SUM(traffic.bytesout) FROM traffic
WHERE (
( traffic.type = 'v4_assignment' AND type_id IN (231, between 20 to 100 ids,265)) OR
( traffic.type = 'v4_host' AND type_id IN (131, ... a lot of ids... ,1506)))
AND traffic.time >= 1343772000 AND traffic.time < 1346450399
GROUP BY traffic.time
ORDER BY traffic.time;
explain
Für die obige Abfrage wird Folgendes ausgegeben:
+----+-------------+---------+-------+---------------+--------------+---------+------+--------+----------------------------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+---------+-------+---------------+--------------+---------+------+--------+----------------------------------------------+
| 1 | SIMPLE | traffic | range | basic_select | basic_select | 8 | NULL | 891319 | Using where; Using temporary; Using filesort |
+----+-------------+---------+-------+---------------+--------------+---------+------+--------+----------------------------------------------+
show indexes from traffic;
+---------+------------+--------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+
| Table | Non_unique | Key_name | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment |
+---------+------------+--------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+
| traffic | 1 | basic_select | 1 | type_id | A | 13835 | NULL | NULL | | BTREE | |
| traffic | 1 | basic_select | 2 | time | A | 18470357 | NULL | NULL | | BTREE | |
| traffic | 1 | basic_select | 3 | type | A | 18470357 | NULL | NULL | | BTREE | |
+---------+------------+--------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+
Diese Abfrage dauert zwischen 30 Sekunden und 30 Minuten. Ich hoffe, ich kann die Dinge mit besseren Indizes oder vielleicht mit einer anderen Abfrage verbessern, aber ich kann es nicht herausfinden.
AKTUALISIEREN:
Auf Anraten der hilfreichen Kommentatoren habe ich einen Primärschlüssel erstellt und den Index hinzugefügt traffic_pk (time, type, type_id, id)
. Leider stellt sich heraus, dass die Kardinalität dieses neuen Index gleich / niedriger als mein ursprünglicher Index (basic_select) ist und MySQL weiterhin meinen ursprünglichen Schlüssel verwendet.
UPDATE 2:
Ich habe meinen ursprünglichen Index gelöscht basic_select
und jetzt EXPLAIN
zeigt der einen höheren rows
Wert, aber weniger Schritte in den EXTRA
Feldern. Auch die Ausführungszeit der Abfrage ist auf unter eine Minute gesunken! (immer noch etwas zu langsam, aber eine große Verbesserung!).
mysql> SHOW CREATE TABLE traffic_test \G;
*************************** 1. row ***************************
Table: traffic_test
Create Table: CREATE TABLE `traffic_test` (
`traffic_id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`type` enum('v4_assignment','v4_host','v6_subnet','v6_assignment','v6_host') NOT NULL,
`type_id` int(11) unsigned NOT NULL,
`time` int(32) unsigned NOT NULL,
`bytesin` bigint(20) unsigned NOT NULL DEFAULT '0',
`bytesout` bigint(20) unsigned NOT NULL DEFAULT '0',
PRIMARY KEY (`time`,`type`,`type_id`,`traffic_id`),
KEY `traffic_id_IDX` (`traffic_id`)
) ENGINE=InnoDB AUTO_INCREMENT=24545159 DEFAULT CHARSET=latin1
Die Indizes in der Tabelle:
mysql> SHOW INDEX FROM traffic;
+--------------+------------+----------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+
| Table | Non_unique | Key_name | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment |
+--------------+------------+----------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+
| traffic_test | 0 | PRIMARY | 1 | time | A | 18 | NULL | NULL | | BTREE | |
| traffic_test | 0 | PRIMARY | 2 | type | A | 38412 | NULL | NULL | | BTREE | |
| traffic_test | 0 | PRIMARY | 3 | type_id | A | 24545609 | NULL | NULL | | BTREE | |
| traffic_test | 0 | PRIMARY | 4 | traffic_id | A | 24545609 | NULL | NULL | | BTREE | |
| traffic_test | 1 | traffic_id_IDX | 1 | traffic_id | A | 24545609 | NULL | NULL | | BTREE | |
+--------------+------------+----------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+
Außerdem habe ich die Abfrage vereinfacht, indem ich nicht Folgendes verwendet habe OR
:
SELECT SQL_NO_CACHE traffic.time, SUM(traffic.bytesin), SUM(traffic.bytesout)
FROM traffic
WHERE traffic.type LIKE 'v4_host' AND type_id IN (131,1974,1976,1514,1516,2767,2730,2731,2732,2733,2734,2769,2994,2709,1,4613,4614,4615,4616,326,1520,2652,1518,1521,1522,1523,1524,1525,2203,1515,1513,1467,1508,1973,1510,1975,1511,1475,1476,1468,1469,1470,1471,1472,1473,1500,1507,1478,1480,1481,1482,1483,1484,1485,1479,1486,1487,1488,1489,1490,1491,1495,1499,1494,2269,1474,1519,2204,2976,1922,1493,1492,1497,1496,1498,1501,1502,1503,1526,1509,1506)
AND traffic.time >= 1342181721
AND traffic.time < 1343391321
GROUP BY traffic.time ASC;
Alte Ausführung dieser Abfrage:
3980 rows in set (6 min 15.27 sec)
Neue Ausführungszeit:
3980 rows in set (24.80 sec)
EXPLAIN-Ausgabe:
+----+-------------+---------+-------+---------------+---------+---------+------+----------+-------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+---------+-------+---------------+---------+---------+------+----------+-------------+
| 1 | SIMPLE | traffic | range | PRIMARY | PRIMARY | 4 | NULL | 12272804 | Using where |
+----+-------------+---------+-------+---------------+---------+---------+------+----------+-------------+
Der Zeilenwert ist immer noch ziemlich hoch. Ich denke, ich kann dies verbessern, indem ich die Reihenfolge von type
und type_id
im Index ändere, da nur 4 Typen möglich sind und viele weitere type_ids.
Ist das eine richtige Annahme?
quelle
Antworten:
1. Tabellenpartitionierung
Aufgrund der Klausel [AND Traffic.time> = 1343772000 AND Traffic.time <1346450399] stelle ich mir vor, dass Sie niemals Daten aus dieser Tabelle löschen oder dass die Tabelle derzeit Daten für mehrere Monate speichert. Die Werte in der Spalte [Zeit] scheinen Unix-Zeitstempel zu sein (1346450399 = Fr, 31. August 2012, 21:59:59 GMT) Partitionieren Sie die Tabelle basierend auf der Zeitspalte. Dies beschleunigt den Datenabruf, da die Datenbank die entsprechende Partition scannt (viel schneller als das Scannen der gesamten Tabelle).
2. Schreiben Sie die Abfrage neu
Aufgrund des "ODER" in Ihrem WHERE-Block verwendet der Optimierer den definierten Index nicht. Versuchen Sie, die Abfrage in zwei Auswahlen aufzuteilen und eine Union zu bilden.
3. Neuer Index basierend auf Datenkardinalität
Aufgrund Ihrer EXPLAIN-Ausgabe wird der verwendete Index nicht verwendet. Möglicherweise, weil der Optimierer entscheidet, dass es einfacher (billiger) ist, einen vollständigen Tabellenscan durchzuführen, als dem Index zu folgen. Außerdem hat in Ihrem aktuellen Index die erste Spalte eine niedrigere Kardinalität als die nächsten beiden. Die erste Spalte in einem Index sollte die Spalte mit der besten (maximalen) Kardinalität sein.
Erstellen Sie einen neuen Index als:
quelle
UNION
Abfrage entspricht nicht dem Original.UNION ALL
wird das beheben, nehme ich an?Ich schlage einen zusammengesetzten Index vor
(time, type, type_id, bytes_in, bytes_out)
.Wenn die
(type_id, time, type)
Kombination eindeutig ist (was ist übrigens der Primärschlüssel der Tabelle?), Können Sie einfach den Primärschlüssel definieren(time, type, type_id)
. Dann wäre der Clustered-Index der Tabelle dieser Primärschlüssel, und Sie würden den obigen zusammengesetzten Index nicht benötigen. Abhängig davon, was Ihre häufigsten Abfragen sind (wenn sie diese habengroup by time
und / oderwhere time >=? and time <?
mögen), werden sie die Effizienz verbessern, da sie den Clustered-Index verwenden können.Sie können die Abfrage auch so umschreiben
=
anstelle vonLIKE
undKombination
GROUP BY
mitORDER BY
(proprietäre MySQL-Syntax, die die Effizienz verbessern kann):Update + Korrektur
Wenn Sie für eine (InnoDB) -Tabelle keinen
PRIMARY
und keinenUNIQUE
Index definiert haben , wird eine versteckte 6-Byte-Spalte erstellt und als Clustered-Index der Tabelle verwendet.Daher ist es möglicherweise besser, eine automatisch inkrementierte 4-Byte-Ganzzahlspalte explizit zu definieren und sie in Kombination mit der
time
Spalte (oder allen 3 obigen Spalten) alsPRIMARY
oderUNIQUE
Schlüssel zu verwenden. Für keinen anderen Zweck als einen Clustered-Index zu haben, der für Ihre Abfragen nützlich ist:oder (um einen engeren Primärschlüssel zu haben):
ein weiterer Vorschlag :
Diese beiden gruppierten Indizes entsprechen in etwa
(time, type, type_id, bytes_in, bytes_out)
den zu Beginn vorgeschlagenen.Der einzige andere Index, der eine bessere Leistung erzielen könnte, ist der
(type, type_id, time, bytes_in, bytes_out)
. Es hängt jedoch davon ab, wie möglicherweisetype_id
diese Listen aufgeführt sind und auf welchen Prozentsatz der Daten sie sich beziehen.quelle
SHOW CREATE TABLE traffic ;
die Ausgabe bei der Frage ausführen und hinzufügen? (oder haben Sie das schon getan?)