Wie erstellt man einen Index für den Datumsteil des Feldes DATETIME in MySql?

70

Wie erstelle ich einen Index für den Datumsteil des Felds DATETIME?

mysql> SHOW COLUMNS FROM transactionlist;
+-------------------+------------------+------+-----+---------+----------------+
| Field             | Type             | Null | Key | Default | Extra          |
+-------------------+------------------+------+-----+---------+----------------+
| TransactionNumber | int(10) unsigned | NO   | PRI | NULL    | auto_increment |
| WagerId           | int(11)          | YES  | MUL | 0       |                |
| TranNum           | int(11)          | YES  | MUL | 0       |                |
| TranDateTime      | datetime         | NO   |     | NULL    |                |
| Amount            | double           | YES  |     | 0       |                |
| Action            | smallint(6)      | YES  |     | 0       |                |
| Uid               | int(11)          | YES  |     | 1       |                |
| AuthId            | int(11)          | YES  |     | 1       |                |
+-------------------+------------------+------+-----+---------+----------------+
8 rows in set (0.00 sec)

TranDateTime wird verwendet, um Datum und Uhrzeit einer Transaktion zu speichern

Meine Tabelle enthält über 1.000.000 Datensätze und die Erklärung

SELECT * FROM transactionlist where date(TranDateTime) = '2008-08-17' 

es dauert lange.

BEARBEITEN:

Schauen Sie sich diesen Blog-Beitrag zum Thema " Warum DATETIME von MySQL vermieden werden kann und sollte " an.

Charles Faiga
quelle
6
Warnkommentar für den Link, den Sie vorgeschlagen haben: Der Beitrag ist so aufgeregt und wütend geschrieben, dass er fast an den Punkt der Kindlichkeit grenzt. Und der Schriftsteller schlägt keine Kritik zurück, während er immer noch erwähnt, dass er hinter dem steht, was er gesagt hat, aber sein Standpunkt fällt mit jedem schlanker. Aber trotzdem keine Zeitverschwendung, wenn Sie die Kommentare lesen.
kommradHomer

Antworten:

64

Wenn ich mich richtig erinnere, wird ein ganzer Tabellenscan ausgeführt, da Sie die Spalte durch eine Funktion führen. MySQL führt die Funktion gehorsam für jede einzelne Spalte aus und umgeht dabei den Index, da der Abfrageoptimierer die Ergebnisse der Funktion nicht wirklich kennen kann.

Was ich tun würde, ist so etwas wie:

SELECT * FROM transactionlist 
WHERE TranDateTime BETWEEN '2008-08-17' AND '2008-08-17 23:59:59.999999';

Das sollte Ihnen alles geben, was am 17.08.2008 passiert ist.

Michael Johnson
quelle
1
Früher dachte ich, diese Verwendung sei nur eine Abkürzung für 'JJJJ-MM-TT 00:00:00'
kommradHomer
3
Ich weiß, dass dies eine alte Antwort ist, aber ich fühle mich gezwungen darauf hinzuweisen, dass MySQL den Zeichenfolgenvergleich für verwendet DATETIME; Ihre Abfrage gibt die korrekten Ergebnisse zurück und enthält keine Zeilen mit TranDateTime=2008-08-18 00:00:00.
Arth
1
Arth, haben Sie eine Quelle, in der MySQL einen Zeichenfolgenvergleich verwendet? War dies in älteren Versionen der Fall? Dies gilt definitiv nicht für MySQL 5.7. Versuchen Sie: Tabelle foobar erstellen (mytime Zeitstempel); in foobar (mytime) Werte einfügen ('2008-08-18 00:00:00'); Wählen Sie * aus der Foobar, in der meine Zeit zwischen '2008-08-17 00:00:00' und '2008-08-18 23:59:59' liegt.
Andreas
Ist BETWEENschneller als mit where TranDateTime >= '2008-08-17' and TranDateTime < '2008-08-18'?
Chloe
Dies ist nicht die richtige Antwort. Die Frage betraf die Indizierung und nicht die Auswahl. Sehen Sie die Antwort stattdessen mit einer generierten Spalte .
ΔO 'deltazero'
13

Eine weitere Option ( relevant für Version 5.7.3 und höher ) besteht darin, eine generierte / virtuelle Spalte basierend auf der datetime-Spalte zu erstellen und diese dann zu indizieren.

CREATE TABLE `table` (
`my_datetime` datetime NOT NULL,
`my_date` varchar(12) GENERATED ALWAYS AS (DATE(`my_datetime`)) STORED,
KEY `my_idx` (`my_date`)
) ENGINE=InnoDB;
Liran Brimer
quelle
1
Warum ist gespeichert und nicht virtuell?
1
Wenn Sie indizieren möchten, muss es gespeichert werden. Ohne Index kann es virtuell sein
Liran Brimer
1
Danke, ich stellte mir vor, ich wurde mit diesem Artikel verwechselt percona.com/blog/2016/03/04/…
Dies sollte die richtige Antwort sein. Ich habe festgestellt, dass der Datumsindex selbst mit BTREE einen Datums- / Uhrzeitindex übertrifft .
ΔO 'deltazero'
Übrigens unterstützt InnoDB heutzutage auch Indizes für VIRTUAL-Spalten.
ΔO 'deltazero'
12

Ich möchte nicht süß klingen, aber eine einfache Möglichkeit wäre, eine neue Spalte hinzuzufügen, die nur den Datumsteil und den Index dazu enthält.

Mike Tunnicliffe
quelle
Ja - und fügen Sie eine Spalte mit nur dem Zeitteil hinzu und entfernen Sie die DATETIME insgesamt.
JBB
Meine aktuelle Lösung besteht darin, einen weiteren Feldaufruf 'Datum' hinzuzufügen. Wenn ich die TranDateTime aktualisiere, wird auch das Datum aktualisiert. Ich habe jetzt einen Index für das 'Datum' und die Abfrage ist viel schneller, da meine Tabelle um + -5% vergrößert wurde
Charles Faiga
9

Sie können nicht nur für den Datumsteil einen Index erstellen. Gibt es einen Grund, warum du musst?

Selbst wenn Sie einen Index nur für den Datumsteil erstellen könnten, würde der Optimierer ihn wahrscheinlich immer noch nicht für die obige Abfrage verwenden.

Ich denke, das wirst du finden

SELECT * FROM transactionlist WHERE TranDateTime BETWEEN '2008-08-17' AND '2008-08-18'

Ist effizient und macht was Sie wollen.

MarkR
quelle
4

Ich weiß nichts über die Besonderheiten von mySql, aber was schadet es, wenn nur das Datumsfeld in seiner Gesamtheit indiziert wird?

Dann suchen Sie einfach:

 select * from translist 
     where TranDateTime > '2008-08-16 23:59:59'
        and TranDateTime < '2008-08-18 00:00:00'

Wenn es sich bei den Indizes um B-Bäume oder um etwas anderes handelt, das vernünftig ist, sollten diese schnell gefunden werden.

Clinton Pierce
quelle
Sie können verwenden >= '2008-08-16' and ... < '2008-08-18'. Die Zeit wird angenommen 00:00:00.
Chloe
Sie meinen:> = '2008-08-17' und ... <'2008-08-18'. Die Zeit wird als 00:00:00
AK
2

Valeriy Kravchuk über eine Feature-Anfrage für genau dieses Problem auf der MySQL-Site sagte, er solle diese Methode verwenden.

"In der Zwischenzeit können Sie Zeichenspalten zum Speichern von DATETIME-Werten als Zeichenfolgen verwenden, wobei nur die ersten N Zeichen indiziert werden. Mit einer sorgfältigen Verwendung von Triggern in MySQL 5 können Sie auf der Grundlage dieser Idee eine relativ robuste Lösung erstellen."

Sie könnten eine Routine schreiben, um diese Spalte ganz einfach hinzuzufügen, und dann mit Triggern diese Spalte synchron halten. Der Index für diese Zeichenfolgenspalte sollte ziemlich schnell sein.

Ray Jenkins
quelle
2

Die einzige und gute Lösung, die ziemlich gut funktioniert, besteht darin, den Zeitstempel als Zeit und nicht als Datum / Uhrzeit zu verwenden. Es wird als INT gespeichert und gut genug indiziert. Persönlich bin ich auf ein solches Problem in der Transaktionstabelle gestoßen, die ungefähr eine Million Datensätze enthält und stark verlangsamt wurde. Schließlich habe ich darauf hingewiesen, dass dies durch ein schlecht indiziertes Feld (Datum / Uhrzeit) verursacht wird. Jetzt läuft es sehr schnell.

Valentin Rusk
quelle
1

Ich weiß nichts über die Besonderheiten von mySQL, aber was schadet es, wenn nur das Datumsfeld in seiner Gesamtheit indiziert wird?

Wenn Sie funktionale Magie für * Bäume verwenden, sind Hashes, ... weg, da Sie die Funktion aufrufen müssen, um Werte zu erhalten. Da Sie die bevorstehenden Ergebnisse jedoch nicht kennen, müssen Sie die Tabelle vollständig scannen.

Es gibt nichts hinzuzufügen.

Vielleicht meinen Sie so etwas wie berechnete (berechnete?) Indizes ... aber bisher habe ich dies nur in Intersystems Caché gesehen. Ich glaube nicht, dass es in relationalen Datenbanken (AFAIK) einen Fall gibt.

Eine gute Lösung ist meiner Meinung nach die folgende (aktualisiertes Clintp-Beispiel):

SELECT * FROM translist 
WHERE TranDateTime >= '2008-08-17 00:00:00.0000'
  AND TranDateTime < '2008-08-18 00:00:00.0000'

Ob Sie verwenden 00:00:00.0000oder 00:00meiner Meinung nach macht keinen Unterschied (ich habe es im Allgemeinen in diesem Format verwendet).

antonia007
quelle
1

datetime LIKE etwas% fängt den Index auch nicht ab.

Verwenden Sie dies: WHERE datetime_field> = curdate ();
Das wird den Index erfassen
und heute: 00: 00: 00 bis heute: 23: 59: 59
abdecken. Fertig.

Dr. Tyrell
quelle
0

Was sagt "erklären"? (Führen Sie EXPLAIN SELECT * FROM Transaktionsliste aus, wobei Datum (TranDateTime) = '2008-08-17')

Wenn Ihr Index aufgrund der Funktion date () nicht verwendet wird, sollte eine Bereichsabfrage schnell ausgeführt werden:

SELECT * FROM Transaktionsliste wobei TranDateTime> = '2008-08-17' UND TranDateTime <'2008-08-18'

Nathan
quelle
1
Wenn Sie date () verwenden, werden Sie den Index nicht treffen. MySQL kann in solchen Funktionsaufrufen keine Indizes verwenden.
JBB
0

Anstatt einen Index basierend auf einer Funktion zu erstellen (falls dies in MySQL überhaupt möglich ist), lassen Sie Ihre where-Klausel einen Bereichsvergleich durchführen. Etwas wie:

Wobei TranDateTime> '2008-08-17 00:00:00' und TranDateTime <'2008-08-17 11:59:59')

Auf diese Weise kann die Datenbank den Index für TranDateTime verwenden (es gibt einen, oder?), Um die Auswahl durchzuführen.

Justsalt
quelle
0

Wenn das Ändern der Tabelle eine Option ist oder Sie eine neue schreiben, sollten Sie Datum und Uhrzeit in separaten Spalten mit den entsprechenden Typen speichern. Sie erhalten Leistung, indem Sie einen viel kleineren Schlüsselbereich und weniger Speicherplatz haben (im Vergleich zu einer Nur-Datum-Spalte, die von einer Datums- / Uhrzeitangabe abgeleitet ist). Dies macht es auch möglich, in zusammengesetzten Schlüsseln zu verwenden, noch vor anderen Spalten.

Im Fall von OP:

+-------------------+------------------+------+-----+---------+----------------+
| Field             | Type             | Null | Key | Default | Extra          |
+-------------------+------------------+------+-----+---------+----------------+
| TransactionNumber | int(10) unsigned | NO   | PRI | NULL    | auto_increment |
| WagerId           | int(11)          | YES  | MUL | 0       |                |
| TranNum           | int(11)          | YES  | MUL | 0       |                |
| TranDate          | date             | NO   |     | NULL    |                |
| TranTime          | time             | NO   |     | NULL    |                |
| Amount            | double           | YES  |     | 0       |                |
| Action            | smallint(6)      | YES  |     | 0       |                |
| Uid               | int(11)          | YES  |     | 1       |                |
| AuthId            | int(11)          | YES  |     | 1       |                |
+-------------------+------------------+------+-----+---------+----------------+
Walf
quelle
-1

Erstellen Sie neue Felder mit nur den Daten convert(datetime, left(date_field,10))und indizieren Sie diese.

Mari
quelle
Warum nicht einfach benutzen date(date_field)?
Chloe