Optimieren der WHERE-Bedingung für das TIMESTAMP-Feld in der MySQL SELECT-Anweisung

8

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)
Locksleyu
quelle
Haben Sie tatsächlich Nullen in diesen 2 Spalten ( time_onund diff_ms)? Was passiert, wenn Sie der Abfrage hinzufügen WHERE ... AND diff_ms IS NOT NULL?
Ypercubeᵀᴹ
Können Sie uns bitte die Ausgabe vonSELECT COUNT(*), COUNT(diff_ms) FROM writetest_table;
ypercubeᵀᴹ
Auch die Erklärung in Ihrem "Update 2" zeigt " Tabelle:writetest_table_old ", während die Abfrage hat from writetest_table. Ist das ein Tippfehler oder führen Sie die Abfrage in einer anderen Tabelle aus?
Ypercubeᵀᴹ

Antworten:

3

Ich glaube ich fange an zu verstehen.

Als ich dich gebeten habe zu rennen

SELECT time_on FROM writetest_table ORDER BY time_on LIMIT 1;

Sie sagten, es sei das, 2015-07-13 15:11:56was Sie in Ihrer WHEREKlausel haben

Als Sie die Abfrage durchgeführt haben

select sum(diff_ms) from writetest_table;

Es wurde ein vollständiger Tabellenscan von 35,8 Millionen Zeilen durchgeführt.

Als Sie die Abfrage durchgeführt haben

select sum(diff_ms) from writetest_table where time_on > ("2015-07-13 15:11:56");

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_onSpalte 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

Wenn die Abfrage heute um Mitternacht war

select sum(diff_ms) from writetest_table where time_on >= ("2015-07-14 00:00:00");

oder sogar mittag heute

select sum(diff_ms) from writetest_table where time_on >= ("2015-07-14 12:00:00");

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.

RolandoMySQLDBA
quelle
Mein einziges Problem ist, wie ich von hier aus vorgehen soll. Ich habe eine Abfrage mit einem Datum durchgeführt, bei dem nur 1 Million Zeilen gefiltert wurden und die Summe nur 1 Sekunde dauerte. Aber gelegentlich muss ich möglicherweise Gesamtsummen für die meisten Daten erstellen. Irgendwelche Vorschläge, wie man damit umgeht? Ich hatte gehofft, dass MySQL klug genug sein würde, um zu wissen, wann der Index verwendet werden soll und wann nicht, aber ich denke, dass es in diesem Fall nicht genügend Informationen gibt.
Locksleyu
Ich wünschte wirklich, es gäbe eine Art Index, der organisiert wurde, um WHERE-Klauseln, die Datumsbereiche angeben, schnell zu machen. Das scheint technisch möglich zu sein, aber ich denke, es wird nicht unterstützt.
Locksleyu
Sie haben viel zu viele Daten in einem so kurzen Bereich. Keine WHERE-Klausel kann jemals kompensiert werden. Warum ? Es ist nicht der Index, der das Problem darstellt. Dies ist die Meinung des MySQL Query Optimizer zum Index. Wenn Sie anfangen, viel mehr Daten zu sammeln (sagen wir etwa zwei Wochen), sollten sich die Indexstatistiken abschwächen und Sie sollten eine Leistungsverbesserung feststellen. Führen Sie nur keine vollständigen Index-Scans durch.
RolandoMySQLDBA
4

Für die spezifische Abfrage:

select sum(diff_ms) 
from writetest_table 
where time_on > '2015-07-13 15:11:56' ;     -- use single quotes, not double

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:

ALTER TABLE writetest_table 
  ADD INDEX time_on__diff_ms__ix      -- pick a name for the index
    (time_on, diff_ms) ;

(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)

ypercubeᵀᴹ
quelle
Ich habe den genauen Index erstellt, den Sie vorgeschlagen haben, und dann die genaue Abfrage ausgeführt, die Sie zuerst in Ihrer Antwort erwähnt haben, aber die Zeit ist jetzt viel schlechter und dauert ungefähr 17 Sekunden (ich habe es mehrmals versucht).
Locksleyu
Ich habe keine Ahnung, was es verursacht. Falls es darauf ankommt, enthält die Tabelle nur 3671 unterschiedliche Werte für time_on (dies liegt daran, wie mein Testskript Daten auffüllt).
Locksleyu
Sie sollten drei (3) Dinge tun: 1. Ausführen ALTER TABLE writetest_table DROP INDEX time_on;, 2) Ausführen ANALYZE TABLE writetest_table;und 3) Ausführen der Abfrage. Geht die Zeit auf 7 Sekunden zurück?
RolandoMySQLDBA
1
Sie sollten auch laufen 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 .
RolandoMySQLDBA
RolandoMySQLDBA - Ich habe Ihre drei Schritte ausprobiert, und ja, die Zeit geht auf 7 Sekunden zurück. Ich habe das erklärt und es heißt, dass der Index verwendet wird. Ich habe immer noch keine Ahnung, warum das Hinzufügen eines solchen Index die Leistung über das Zweifache so schlecht machen könnte.
Locksleyu