Mysql
 sql >> Datenbank >  >> RDS >> Mysql

Eine sehr einfache AVG()-Aggregationsabfrage auf dem MySQL-Server dauert lächerlich lange

Um die Anzahl der Zeilen mit einem bestimmten Datum zu zählen, muss MySQL diesen Wert im Index finden (was ziemlich schnell geht, schließlich sind Indizes dafür gemacht) und dann die nachfolgenden Einträge des Index lesen. em> bis es den nächsten Termin findet. Abhängig vom Datentyp von esi , summiert sich dies auf das Lesen einiger MB an Daten, um Ihre 700.000 Zeilen zu zählen. Das Lesen einiger MB nimmt nicht viel Zeit in Anspruch (und diese Daten können sogar bereits im Pufferpool zwischengespeichert sein, je nachdem, wie oft Sie den Index verwenden).

Um den Durchschnitt für eine Spalte zu berechnen, die nicht im Index enthalten ist, verwendet MySQL wiederum den Index, um alle Zeilen für dieses Datum zu finden (wie zuvor). Aber zusätzlich muss es für jede gefundene Zeile die tatsächlichen Tabellendaten für diese Zeile lesen, was bedeutet, dass der Primärschlüssel verwendet wird, um die Zeile zu lokalisieren, einige Bytes zu lesen und dies 700.000 Mal zu wiederholen. Dieser "wahlfreier Zugriff" ist viel langsamer als das sequentielle Lesen im ersten Fall. (Dies wird noch schlimmer durch das Problem, dass "einige Bytes" der innodb_page_size (standardmäßig 16 KB), sodass Sie möglicherweise bis zu 700 KB * 16 KB =11 GB lesen müssen, verglichen mit "einigen MB" für count(*); und abhängig von Ihrer Speicherkonfiguration werden einige dieser Daten möglicherweise nicht zwischengespeichert und müssen von der Festplatte gelesen werden.)

Eine Lösung hierfür besteht darin, alle verwendeten Spalten in den Index (einen "überdeckenden Index") aufzunehmen, z. Erstellen Sie einen Index am date, 01 . Dann muss MySQL nicht auf die Tabelle selbst zugreifen und kann ähnlich wie bei der ersten Methode nur den Index lesen. Die Größe des Index wird etwas zunehmen, sodass MySQL „etwas mehr MB“ lesen muss (und die avg ausführen muss -operation), aber es sollte immer noch eine Sache von Sekunden sein.

In den Kommentaren haben Sie erwähnt, dass Sie den Durchschnitt über 24 Spalten berechnen müssen. Wenn Sie den avg berechnen möchten für mehrere Spalten gleichzeitig benötigen Sie einen abdeckenden Index für alle, z. date, 01, 02, ..., 24 Tabellenzugriff zu verhindern. Beachten Sie, dass ein Index, der alle Spalten enthält, genauso viel Speicherplatz benötigt wie die Tabelle selbst (und es dauert lange, einen solchen Index zu erstellen). Daher kann es davon abhängen, wie wichtig diese Abfrage ist, ob sie diese Ressourcen wert ist.

Um das MySQL-Limit von 16 Spalten pro Index zu umgehen , könnten Sie es in zwei Indizes (und zwei Abfragen) aufteilen. Erstellen Sie z. die Indizes date, 01, .., 12 und date, 13, .., 24 , dann verwenden Sie

select * from (select `date`, avg(`01`), ..., avg(`12`) 
               from mytable where `date` = ...) as part1
cross join    (select avg(`13`), ..., avg(`24`) 
               from mytable where `date` = ...) as part2;

Stellen Sie sicher, dass Sie dies gut dokumentieren, da es keinen offensichtlichen Grund gibt, die Abfrage auf diese Weise zu schreiben, aber es könnte sich lohnen.

Wenn Sie immer nur über eine einzelne Spalte mitteln, könnten Sie 24 separate Indizes hinzufügen (am date, 01 , date, 02 , ...), benötigen zwar insgesamt noch mehr Platz, könnten aber etwas schneller sein (da sie einzeln kleiner sind). Der Pufferpool bevorzugt jedoch möglicherweise immer noch den vollständigen Index, abhängig von Faktoren wie Nutzungsmustern und Speicherkonfiguration, sodass Sie ihn möglicherweise testen müssen.

Seit date Teil Ihres Primärschlüssels ist, können Sie den Primärschlüssel auch in date, esi ändern . Wenn Sie die Daten anhand des Primärschlüssels finden, benötigen Sie keinen zusätzlichen Schritt, um auf die Tabellendaten zuzugreifen (da Sie bereits auf die Tabelle zugreifen), sodass das Verhalten dem des abdeckenden Index ähnelt. Dies ist jedoch eine erhebliche Änderung an Ihrer Tabelle und kann sich auf alle anderen Abfragen auswirken (die z. B. esi verwenden um Zeilen zu lokalisieren), also muss es sorgfältig überlegt werden.

Wie Sie bereits erwähnt haben, besteht eine weitere Option darin, eine Übersichtstabelle zu erstellen, in der Sie vorberechnete Werte speichern, insbesondere wenn Sie keine Zeilen für vergangene Daten hinzufügen oder ändern (oder sie mit einem Trigger auf dem neuesten Stand halten können).