it-swarm.com.de

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

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)
8
Locksleyu

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;

Du hast gesagt, es war 2015-07-13 15:11:56, die Sie in Ihrer WHERE -Klausel 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.

Die 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. Zusätzlich time_on Spalte muss 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 heute mittag

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.

3
RolandoMySQLDBA

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)
nd wirklich, ändern Sie die Engine des Tisches auf InnoDB. Es ist 2015 und MyISAMs Beerdigung war vor einigen Jahren.
(/ Rant)

4
ypercubeᵀᴹ