Frage

Wie select count(*) mit group by beschleunigen?
Es ist zu langsam und wird sehr häufig.
Ich habe ein großes Problem mit select count(*) und group by mit einem Tisch mehr als 3.000.000 Zeilen aufweist.

select object_title,count(*) as hot_num   
from  relations 
where relation_title='XXXX'   
group by object_title  

relation_title , object_title ist varchar. wo relation_title = 'XXXX' , die mehr als eine Million Zeilen zurückgibt, zu den Indizes führen auf object_title konnte nicht gut funktionieren.

War es hilfreich?

Lösung

Hier sind einige Dinge, die ich versuchen würde, in der Reihenfolge der zunehmenden Schwierigkeit:

(einfacher) - Achten Sie darauf, das Recht haben, zu verdecken Index

CREATE INDEX ix_temp ON relations (relation_title, object_title);

Dies sollte maximieren perf Ihr vorhandenes Schema gegeben, da (es sei denn, Ihre Version von MySQL-Optimierer wirklich dumm ist!) Es wird die Menge an I / O minimieren benötigt, um Ihre Abfrage zu erfüllen (im Gegensatz zu, wenn der Index in der umgekehrten Reihenfolge, wo der gesamte Index gescannt werden müssen) und es wird die Abfrage abdecken, so dass Sie nicht den gruppierten Index berühren wird.

(ein wenig härter) - stellen Sie sicher, dass Ihre varchar Felder so klein wie möglich sind

Eine der perf Herausforderungen mit varchar Indizes für MySQL ist, dass, wenn eine Abfrage der Verarbeitung, die vollständig erklärt Größe des Feldes wird in dem RAM gezogen werden. Wenn Sie also einen varchar haben (256), sondern verwenden nur 4 Zeichen, zahlst du immer noch die 256-Byte-RAM-Auslastung, während die Abfrage verarbeitet wird. Autsch! Also, wenn Sie Ihre varchar Grenzen leicht schrumpfen kann, sollte dies Ihre Abfragen beschleunigen.

(härter) - Normalisieren

30% der Zeilen einen einzelnen String-Wert ist ein klarer Schrei in einer anderen Tabelle Normalisierung so dass Sie nicht Strings Millionen mal zu duplizieren. Betrachten wir die Normalisierung in drei Tabellen und mit Integer-IDs sie zu verbinden.

In einigen Fällen können Sie unter der Decke normalisieren und die Normalisierung mit Blick verbergen, die den Namen der aktuellen Tabelle überein ... dann müssen Sie nur Ihre INSERT machen / UPDATE / DELETE-Abfragen bewusst die Normalisierung kann aber verlassen Ihre SELECTs allein.

(am härtesten) - Hash der Zeichenfolge Spalten und Index der Hashes

Wenn Mittel Normalisierung zu viel Code zu ändern, aber Sie können das Schema ein wenig ändern, können Sie 128-Bit-Hash-Werte für Ihre String-Spalten zu prüfen, Erstellen (mit der MD5-Funktion ). In diesem Fall (im Gegensatz zu Normalisierung) Sie müssen alle Ihre Fragen nicht ändern, nur die Einfügungen und einige der SELECTs. Wie auch immer, Sie wollen Ihre String-Felder Hash und dann einen Index für die Hashes erstellen, zum Beispiel

CREATE INDEX ix_temp ON relations (relation_title_hash, object_title_hash);

Beachten Sie, dass Sie mit der SELECT-rumspielen werden müssen, um sicherzustellen, dass Sie die Berechnung über den Hash-Index tun, und nicht in dem Clustered-Index (erforderlich Ziehen den aktuellen Textwert von object_title zu lösen, um die Abfrage zu erfüllen ).

Auch wenn relation_title eine kleine varchar Größe hat aber Objekt Titel hat eine lange Größe, dann können Sie möglicherweise nur object_title Hash und den Index auf (relation_title, object_title_hash) erstellen.

Beachten Sie, dass diese Lösung hilft nur dann, wenn eine oder beide dieser Felder auf die Größe der Hash-Werte sehr lang ist relativ.

Beachten Sie auch, dass es interessante Fall-Empfindlichkeit / Sortierungs Auswirkungen von Hashing, da der Hash eines Klein Zeichenfolge nicht das gleiche wie ein Hash von einem Groß ist. So müssen Sie sicherstellen, dass Sie die Kanonisierung auf die Saiten anwenden, bevor in anderen Worten Hashing them--, nur Hash Klein, wenn Sie in Groß- und Kleinschreibung DB sind. Sie können sich auch von Anfang an oder Endräumen trimmen möchten, je nachdem, wie Sie Ihre DB Griffe Vorder- / Hinterräumen.

Andere Tipps

Indizierung der Spalten in der GROUP BY-Klausel die erste Sache sein würde, um zu versuchen, einen zusammengesetzten Index. Eine Abfrage wie diese kann möglicherweise nur die Indexdaten beantwortet werden, wodurch die Notwendigkeit vermieden wird, die Tabelle überhaupt zu scannen. Da die Einträge in dem Index sortiert sind, muß das DBMS sollte keine separaten Sortier als Teil der Gruppenverarbeitung auszuführen. Allerdings wird die Index-Updates auf den Tisch verlangsamen, so vorsichtig sein mit diesem, wenn Ihre Tabelle schweres Updates erfährt.

Wenn Sie InnoDB für den Tabellenspeicher verwenden, werden die Zeilen der Tabelle physisch durch den Primärschlüsselindex geclustert werden. Wenn die (oder ein führender Teil davon) geschieht Ihre GROUP BY-Taste entsprechen, das sollte eine Abfrage wie dies, weil verknüpften Datensätze beschleunigen werden zusammen abgerufen werden. Auch dies vermeidet eine separate Art durchführen zu müssen.

In der Regel Bitmap-Indizes wäre eine weitere Alternative sein, aber MySQL derzeit nicht diese unterstützen, soweit ich weiß.

Eine materialisierte Ansicht ein weiterer möglicher Ansatz wäre, aber auch dies ist nicht direkt in MySQL unterstützt. Wenn Sie jedoch nicht die COUNT Statistiken erforderlich sein völlig up-to-date, könnten Sie in regelmäßigen Abständen eine CREATE TABLE ... AS SELECT ... Anweisung ausführen, um manuell die Ergebnisse zwischenzuspeichern. Das ist ein bisschen hässlich, da es nicht transparent ist, kann aber in Ihrem Fall akzeptabel sein.

Sie können auch eine logische-Level-Cache-Tabelle mit Trigger halten. Diese Tabelle würde eine Spalte für jede Spalte in der GROUP BY-Klausel, mit einer Count-Spalte für die Anzahl der Zeilen für diesen bestimmten Gruppierungsschlüsselwert zu speichern. Jedes Mal, wenn eine Zeile hinzugefügt wird oder in der Basistabelle aktualisiert, eingefügt oder Inkrement / Dekrement des Zähler Zeile in der Übersichtstabelle für diesen bestimmten Gruppierungsschlüssel. Dies kann besser sein als die gefälschte Ansicht Ansatz verwirklicht, als die zwischengespeicherte Zusammenfassung immer up-to-date sein, und jedes Update wird schrittweise erfolgen und weniger eine Ressource Auswirkungen haben sollte. Ich denke, Sie müßten für Sperrenkonflikte auf der Cache-Tabelle aufgepasst, aber.

Wenn Sie InnoDB haben, COUNT (*) und andere Aggregatfunktion eine Tabelle-Scan tun. Ich sehe ein paar Lösungen hier:

  1. Verwenden Sie Trigger und Speicher-Aggregate in einer separaten Tabelle. Pro: Integrität. Nachteile: langsames Updates
  2. Verwenden Sie Verarbeitungswarteschlangen. Vorteile: schneller Updates. Nachteile:. Alten Zustand kann andauern, bis die Warteschlange verarbeitet wird, so dass der Benutzer einen Mangel an Integrität fühlen
  3. Voll trennt die Speicherzugriffsschicht und speichern Aggregate in einer separaten Tabelle. Die Speicherschicht wird sich der Datenstruktur sein und Deltas zu tun, anstatt volle Zählungen anwenden können. Wenn Sie zum Beispiel bieten eine „addObject“ -Funktionalität innerhalb, dass Sie wissen, wann ein Objekt hinzugefügt wurde und damit das Aggregat betroffen sein würde. Dann tun Sie nur eine update table set count = count + 1. Vorteile: schnelle Updates, Integrität (Sie können eine Sperre verwenden, obwohl bei mehreren Clients denselben Datensatz ändern können). Nachteile:. Sie Paar ein bisschen Business-Logik und Speicherung

Ich sehe, dass einige Personen gefragt haben, welche Engine Sie für die Abfrage verwendet haben. Ich würde Ihnen empfehlen, MyISAM für folgende reasions verwenden:

InnoDB - @Sorin Mocanu richtig erkannt, dass Sie unabhängig von Indizes eine vollständige Tabelle scannen tun werden.

MyISAM - hält immer die aktuelle Zeile praktisch zählen.

Schließlich ist, wie @justin angegeben, stellen Sie sicher, dass Sie den richtigen abdeckenden Index haben:

CREATE INDEX ix_temp ON relations (relation_title, object_title);

Test  count (myprimaryindexcolumn) und vergleichen Sie die Leistung Ihren count (*)

gibt es einen Punkt, an dem Sie wirklich brauchen mehr RAM / CPU / IO. Möglicherweise haben Sie die für Ihre Hardware getroffen.

Ich werde beachten Sie, dass es in der Regel nicht wirksam Indizes verwendet werden (es sei denn, sie sind Abdeckung) zum Abfragen, die mehr als 1-2% der gesamten Zeilen in einer Tabelle getroffen. Wenn Ihre große Abfrage Index tun und Lookups Lesezeichen sucht, könnte es sein, wegen eines im Cache gespeicherten Plan, der von nur einem Tag insgesamt Abfrage war. versuchen Sie, in WITH (INDEX = 0), um eine Tabelle Scan zu erzwingen und sehen, ob es schneller ist.

nehmen diese ab: http://www.microsoft.com/communities/newsgroups/en-us/default.aspx?dg=microsoft.public.sqlserver.programming&tid=4631bab4-0104- 47AA-B548-e8428073b6e6 & cat = & lang = & cr = & sloc = & p = 1

Wenn Sie, was die Größe der gesamten Tabelle, sollten Sie die Meta-Tabellen oder Info-Schema abfragen (die auf jedem DBMS existieren ich weiß, aber ich bin mir nicht sicher über MySQL). Wenn Ihre Abfrage selektiv ist, müssen Sie sicherstellen, dass ein Index für es da ist.

AFAIK gibt es nichts mehr Sie tun können.

würde ich vorschlagen, Daten zu archivieren, es sei denn es einen bestimmten Grund, warum es in der Datenbank zu halten ist oder Sie die Daten und Abfragen ausführen separat partitionieren können.

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top