Ich arbeite an einem Schema für ein Analysesystem, das die Nutzungszeiten verfolgt, und es besteht die Notwendigkeit, die Gesamtnutzungszeit in einem bestimmten Datumsbereich anzuzeigen.
Um ein einfaches Beispiel zu nennen: Diese Art von Abfrage wird häufig ausgeführt:
select sum(diff_ms) from writetest_table where time_on > ("2015-07-13 15:11:56");
Diese Abfrage dauert in der Regel etwa 7 Sekunden für eine Tabelle, die stark gefüllt ist. Es hat ~ 35 Millionen Zeilen, MyISAM unter MySQL läuft unter Amazon RDS (db.m3.xlarge).
Wenn Sie die WHERE-Klausel entfernen, dauert die Abfrage nur 4 Sekunden, und durch Hinzufügen einer zweiten Klausel (time_off> XXX) werden zusätzliche 1,5 Sekunden hinzugefügt, wodurch sich die Abfragezeit auf 8,5 Sekunden erhöht.
Da ich weiß, dass diese Art von Abfragen häufig durchgeführt wird, möchte ich die Dinge so optimieren, dass sie schneller sind, idealerweise unter 5 Sekunden.
Ich habe zunächst einen Index für time_on hinzugefügt, und obwohl dies eine WHERE "=" - Abfrage drastisch beschleunigte, hatte dies keine Auswirkungen auf die ">" - Abfrage. Gibt es eine Möglichkeit, einen Index zu erstellen, der die WHERE ">" - oder "<" - Abfragen beschleunigt?
Oder wenn es andere Vorschläge zur Durchführung dieser Art von Abfrage gibt, lassen Sie es mich bitte wissen.
Hinweis: Ich verwende das Feld "diff_ms" als Denormalisierungsschritt (es entspricht time_off - time_on), wodurch die Leistung der Aggregation um etwa 30% bis 40% verbessert wird.
Ich erstelle den Index mit diesem Befehl:
ALTER TABLE writetest_table ADD INDEX time_on (time_on) USING BTREE;
Wenn Sie "EXPLAIN" für die ursprüngliche Abfrage ausführen (mit "time_on>"), wird angegeben, dass time_on ein "möglicher_ Schlüssel" und der select_type "EINFACH" ist. In der Spalte "extra" steht "Using where" und "type" ist "ALL". Nach dem Hinzufügen des Index wird in der Tabelle angegeben, dass "time_on" der Schlüsseltyp "MUL" ist, was korrekt erscheint, da dieselbe Zeit zweimal vorhanden sein kann.
Hier ist das Tabellenschema:
CREATE TABLE `writetest_table` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`sessionID` int(11) DEFAULT NULL,
`time_on` timestamp NULL DEFAULT NULL,
`time_off` timestamp NULL DEFAULT NULL,
`diff_ms` int(11) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `time_on` (`time_on`)
) ENGINE=MyISAM AUTO_INCREMENT=50410902 DEFAULT CHARSET=latin1;
UPDATE: Ich habe den folgenden Index basierend auf der Antwort von ypercube erstellt, aber dies erhöht die Abfragezeit für die erste Abfrage auf ungefähr 17 Sekunden!
ALTER TABLE writetest_table ADD INDEX time_on__diff_ms__ix (time_on, diff_ms) ;
UPDATE 2: EXPLAIN-Ausgabe
mysql> explain select sum(diff_ms) from writetest_table where time_on > '2015-07-13 15:11:56';
+----+-------------+---------------------+-------+----------------------+----------------------+---------+------+----------+--------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+---------------------+-------+----------------------+----------------------+---------+------+----------+--------------------------+
| 1 | SIMPLE | writetest_table_old | index | time_on__diff_ms__ix | time_on__diff_ms__ix | 10 | NULL | 35831102 | Using where; Using index |
+----+-------------+---------------------+-------+----------------------+----------------------+---------+------+----------+--------------------------+
1 row in set (0.00 sec)
Update 3: Ergebnis der angeforderten Abfrage
mysql> SELECT time_on FROM writetest_table ORDER BY time_on LIMIT 1;
+---------------------+
| time_on |
+---------------------+
| 2015-07-13 15:11:56 |
+---------------------+
1 row in set (0.01 sec)
time_on
unddiff_ms
)? Was passiert, wenn Sie der Abfrage hinzufügenWHERE ... AND diff_ms IS NOT NULL
?SELECT COUNT(*), COUNT(diff_ms) FROM writetest_table;
writetest_table_old
", während die Abfrage hatfrom writetest_table
. Ist das ein Tippfehler oder führen Sie die Abfrage in einer anderen Tabelle aus?Antworten:
Ich glaube ich fange an zu verstehen.
Als ich dich gebeten habe zu rennen
Sie sagten, es sei das,
2015-07-13 15:11:56
was Sie in IhrerWHERE
Klausel habenAls Sie die Abfrage durchgeführt haben
Es wurde ein vollständiger Tabellenscan von 35,8 Millionen Zeilen durchgeführt.
Als Sie die Abfrage durchgeführt haben
Es wurde ein vollständiger Index-Scan von 35,8 Millionen Zeilen durchgeführt.
Es ist absolut sinnvoll, dass die Abfrage ohne die WHERE-Klausel schneller ist. Warum ?
Der Tabellenscan würde 35,8 Millionen Zeilen in einem linearen Durchgang lesen.
Das EXPLAIN für die Abfrage mit dem WHERE ergab ebenfalls 35,8 Millionen Zeilen. Ein Index-Scan würde sich etwas anders verhalten. Während der BTREE die Reihenfolge der Schlüssel beibehält, ist es für Entfernungsscans schrecklich. In Ihrem speziellen Fall führen Sie den schlechtesten Bereichsscan durch, der die gleiche Anzahl von BTREE-Einträgen enthält, wie Zeilen in der Tabelle vorhanden sind. MySQL muss die BTREE-Seiten (zumindest über die Blattknoten hinweg) durchlaufen, um die Werte zu lesen. Außerdem muss die
time_on
Spalte auf dem Weg in der vom Index vorgegebenen Reihenfolge verglichen werden. Daher müssen auch Nicht-Blatt-BTREE-Knoten durchlaufen werden.Bitte beachten Sie meine Beiträge auf BTREEs
Aug 06, 2013
: Wenn in MySQL Spalte X eindeutige Werte hat, was ist der Unterschied zwischen dem UNIQUE-Index und dem B-Tree-Index?Jun 28, 2012
: Vorteile von BTREE in MySQLWenn die Abfrage heute um Mitternacht war
oder sogar mittag heute
es sollte weniger Zeit dauern.
MORAL DER GESCHICHTE: Verwenden Sie keine WHERE-Klausel, die einen geordneten Bereichsscan durchführt, der der Anzahl der Zeilen in der Zieltabelle entspricht.
quelle
Für die spezifische Abfrage:
Ein Index auf
(time_on, diff_ms)
wäre die beste Option. Wenn die Abfrage häufig genug ausgeführt wird oder ihre Effizienz für Ihre Anwendung von entscheidender Bedeutung ist, fügen Sie diesen Index hinzu:(Nicht im Zusammenhang mit der Frage)
Und wirklich, ändern Sie die Engine der Tabelle in InnoDB. Es ist 2015 und MyISAMs Beerdigung war vor einigen Jahren.
(/schimpfen)
quelle
ALTER TABLE writetest_table DROP INDEX time_on;
, 2) AusführenANALYZE TABLE writetest_table;
und 3) Ausführen der Abfrage. Geht die Zeit auf 7 Sekunden zurück?EXPLAIN select sum(diff_ms) from writetest_table where time_on > ("2015-07-13 15:11:56");
. Wird der neue Index verwendet? Wenn es nicht verwendet wird, würde ich sagen, dass es Ihre Schlüsselpopulation ist, insbesondere wenn Ihre früheste time_on erst vor wenigen Tagen liegt. Da die Anzahl der Zeilen mit deutlicheren Tagen zunimmt, sollte sich die Schlüsselverteilung abflachen und die EXPLAIN sollte besser sein .