2009-06-23 7 views
22

Wie beschleunigt man select count(*) mit group by?
Es ist zu langsam und wird sehr häufig verwendet.
Ich habe ein großes Problem mit select count(*) und group by mit einer Tabelle mit mehr als 3.000.000 Zeilen.Beschleunigen Sie "Anzahl wählen (*)" mit "Gruppieren nach" und "Wo"?

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 1.000.000 Zeilen zurückgibt, führen zu den Indizes object_title konnte nicht gut funktionieren.

+0

Könnten Sie bitte weitere Details z. die ganze Auswahl und die Tabellenstruktur? Noch ein erster Versuch: Verwenden Sie richtig Indizes? – Kosi2801

+0

Ich habe unten ein paar mögliche Lösungen hinzugefügt, aber ich stimme Kosi zu, dass die Definition der Tabellen (insbesondere die Länge der Varchar-Spalten!) Und die Index-Definitionen sehr hilfreich wären, um dies zu diagnostizieren. –

+0

Sind Beziehungen eine Innodb- oder MyISAM-Tabelle? –

Antwort

47

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

(einfacher) - Stellen Sie sicher, haben Sie das Recht Abdeckung Index

CREATE INDEX ix_temp ON relations (relation_title, object_title); 

Dies sollte die Leistung für Ihr vorhandenes Schema maximieren, da (solange Ihre Version des mySQL-Optimierers nicht wirklich dumm ist!) Es die Anzahl der benötigten E/As minimiert, um Ihre Anfrage zu erfüllen (anders als wenn der Index in der umgekehrten Reihenfolge steht) Index muss gescannt werden) und deckt die Abfrage ab, sodass Sie den Clustered Index nicht berühren müssen.

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

Einer der perf Herausforderungen mit varchar Indizes für MySQL ist, dass bei der Verarbeitung einer Abfrage, die vollständig erklärt Größe der Feld wird in RAM gezogen werden. Wenn Sie also varchar (256) verwenden, aber nur 4 Zeichen verwenden, zahlen Sie immer noch die 256-Byte-RAM-Auslastung, während die Abfrage verarbeitet wird. Autsch! Wenn Sie Ihre Varchar-Limits einfach reduzieren können, sollte dies Ihre Abfragen beschleunigen.

(härter) - Normalisieren

30% Ihrer Zeilen einen einzelnen String-Wert ist ein klarer Schrei in einer anderen Tabelle zu normalisieren, so dass Sie keine Strings Millionen mal zu duplizieren. Ziehen Sie in Betracht, in drei Tabellen zu normalisieren und ganzzahlige IDs zu verwenden, um sie zu verbinden.

In einigen Fällen können Sie unter den Abdeckungen normalisieren und die Normalisierung mit Ansichten ausblenden, die mit dem Namen der aktuellen Tabelle übereinstimmen ... dann müssen Sie nur Ihre INSERT/UPDATE/DELETE-Abfragen auf die Normalisierung aufmerksam machen, können aber Lassen Sie Ihre SELECTs in Ruhe.

(am härtesten) - Hash der Zeichenfolge Spalten und Index die 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 zu prüfen, die Schaffung für Ihre String-Spalten (mit der MD5 function). In diesem Fall müssen Sie (anders als bei der Normalisierung) nicht alle Ihre Abfragen ändern, nur die INSERTs und einige der SELECTs. Wie auch immer, Sie wollen Ihre String-Felder hashen und dann einen Index für die Hashes erstellen, z.

CREATE INDEX ix_temp ON relations (relation_title_hash, object_title_hash); 

Beachten Sie, dass Sie benötigen, um mit der SELECT zu spielen, um sicherzustellen, dass Sie die Berechnung über den Hash-Index tun, und nicht in der Clustered-Index (erforderlich Ziehen des aktuellen Textwert von object_title zu lösen, um um die Anfrage zu erfüllen).

Wenn relation_title eine kleine varchar-Größe hat, aber der Objekttitel eine lange Größe hat, können Sie möglicherweise nur object_title hashen und den Index auf (relation_title, object_title_hash) erstellen.

Beachten Sie, dass diese Lösung nur hilft, wenn eines oder beide dieser Felder im Verhältnis zur Größe der Hashes sehr lang ist.

Beachten Sie auch, dass es beim Hashing interessante Auswirkungen auf die Groß- und Kleinschreibung/Kollation gibt, da der Hash eines Kleinbuchstaben-Strings nicht mit dem Hash eines Großbuchstabens übereinstimmt. Sie müssen also sicherstellen, dass Sie die Kanonisierung auf die Strings anwenden, bevor Sie sie hash- en - in anderen Worten, nur Kleinbuchstaben, wenn Sie in einer Groß-/Kleinschreibung zwischen Groß- und Kleinschreibung unterscheiden. Je nachdem, wie Ihre DB führende/nachstehende Leerzeichen behandelt, möchten Sie möglicherweise auch Leerzeichen am Anfang oder Ende beschneiden.

+0

Der Deckungsindex Justin erwähnt hier ist absolut der beste Weg, um gute Leistung aus dieser Abfrage zu bekommen. – BradC

+0

Danke, sehr nützlich – mOna

+0

Ein Feld CHAR ist eine feste Länge und VARCHAR ist ein Feld variabler Länge. Dies bedeutet, dass die Speicheranforderungen unterschiedlich sind - ein CHAR nimmt immer denselben Speicherplatz ein, unabhängig davon, was Sie speichern, während die Speicheranforderungen für VARCHAR je nach der gespeicherten Zeichenfolge variieren. Also, machen Varchar Feld so klein wie möglich würde nicht viel Performance-Einfluss geben. – NPE

0

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

Ich werde feststellen, dass es normalerweise nicht effektiv ist, Indizes zu verwenden (außer sie sind abdecken) für Abfragen, die mehr als 1-2% der gesamten Zeilen in einer Tabelle treffen. Wenn Ihre große Abfrage Index-Suchvorgänge und Lesezeichen-Lookups durchführt, könnte es aufgrund eines zwischengespeicherten Plans sein, der nur aus einer Tag-Gesamtabfrage stammt. Versuchen Sie, in WITH (INDEX = 0) hinzuzufügen, um einen Tabellenscan zu erzwingen und festzustellen, 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

+0

Ich dachte, das war MS SQL zu starten, aber das Poster hat die mysql-Tag hinzugefügt ... –

+0

Beachten Sie, dass die Frage mit "mysql" nicht "mssql" markiert ist. – Kosi2801

+0

ja, 'mysql'. Ich habe versucht "force index (primary)", dass mysql den Index nicht selbst verwendet. Es ist effektiv, 20s bis 15s. –

0

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

AFAIK gibt es nichts mehr, was Sie tun können.

10

Die Indexierung der Spalten in der GROUP BY-Klausel wäre die erste Sache, die versucht wird, einen zusammengesetzten Index zu verwenden. Eine solche Abfrage kann möglicherweise nur mit den Indexdaten beantwortet werden, so dass die Tabelle überhaupt nicht gescannt werden muss. Da die Datensätze im Index sortiert sind, sollte das DBMS keine separate Sortierung als Teil der Gruppenverarbeitung durchführen müssen. Der Index verlangsamt jedoch die Aktualisierung der Tabelle. Seien Sie also vorsichtig, wenn in Ihrer Tabelle umfangreiche Aktualisierungen vorgenommen werden.

Wenn Sie InnoDB für den Tabellenspeicher verwenden, werden die Zeilen der Tabelle durch den Primärschlüsselindex physisch gruppiert. Wenn dieser (oder ein führender Teil davon) mit Ihrem GROUP BY-Schlüssel übereinstimmt, sollte dies eine Abfrage wie diese beschleunigen, da verwandte Datensätze zusammen abgerufen werden. Dies vermeidet wiederum, dass eine separate Sortierung durchgeführt werden muss.

Im Allgemeinen wären Bitmap-Indizes eine andere effektive Alternative, aber MySQL unterstützt diese derzeit nicht, soweit ich weiß.

Eine materialisierte Ansicht wäre ein weiterer möglicher Ansatz, aber auch dies wird nicht direkt in MySQL unterstützt. Wenn die COUNT-Statistiken jedoch nicht vollständig auf dem neuesten Stand sein müssen, können Sie in regelmäßigen Abständen eine CREATE TABLE ... AS SELECT ...-Anweisung ausführen, um die Ergebnisse manuell zwischenzuspeichern. Dies ist ein bisschen hässlich, da es nicht transparent ist, aber in Ihrem Fall akzeptabel sein kann.

Sie können auch eine Cache-Tabelle mit logischen Ebenen mithilfe von Triggern verwalten. Diese Tabelle enthält für jede Spalte in Ihrer GROUP BY-Klausel eine Spalte mit einer Count-Spalte zum Speichern der Anzahl der Zeilen für diesen bestimmten Gruppierungsschlüsselwert.Jedes Mal, wenn eine Zeile in der Basistabelle hinzugefügt oder aktualisiert wird, fügen Sie die Zählerzeile in der Übersichtstabelle für diesen bestimmten Gruppierungsschlüssel ein oder inkrementieren/dekrementieren Sie die Zählerzeile. Dies ist möglicherweise besser als der Ansatz der gefälschten materialisierten Ansicht, da die zwischengespeicherte Zusammenfassung immer auf dem neuesten Stand ist und jede Aktualisierung inkrementell erfolgt und weniger Auswirkungen auf die Ressourcen haben sollte. Ich denke, dass Sie auf Sperrkonflikt auf der Cache-Tabelle jedoch achten müssen.

+1

Kleinere Spalten können helfen: Wenn der Tabellenscan nicht zu vermeiden ist, wird eine kleinere Tabelle weniger Zeit zum Scannen benötigen. Vielleicht könnten Sie die Tabellenstruktur und einige Beispieldaten zusammen mit der genauen Abfrage veröffentlichen. – cheduardo

6

Wenn Sie InnoDB haben, wird mit count (*) und jeder anderen Aggregatfunktion ein Tabellenscan durchgeführt. Ich sehe hier ein paar Lösungen:

  1. Verwenden Sie Trigger und speichern Sie Aggregate in einer separaten Tabelle. Vorteile: Integrität. Nachteile: langsame Aktualisierungen
  2. Verwenden Sie Verarbeitungswarteschlangen. Vorteile: schnelle Updates. Nachteile: Der alte Zustand kann bestehen bleiben, bis die Warteschlange verarbeitet wird, so dass der Benutzer einen Mangel an Integrität empfindet.
  3. Den Speicherzugriffslayer vollständig trennen und Aggregate in einer separaten Tabelle speichern. Die Speicherschicht wird die Datenstruktur kennen und kann Deltas anwenden, anstatt vollständige Zählungen durchzuführen. Wenn Sie beispielsweise eine "addObject" -Funktionalität angeben, wissen Sie, wenn ein Objekt hinzugefügt wurde und somit das Aggregat betroffen ist. Dann machst du nur einen update table set count = count + 1. Vorteile: schnelle Updates, Integrität (Sie möchten vielleicht eine Sperre verwenden, falls mehrere Clients den gleichen Datensatz ändern können). Nachteile: Sie verbinden ein wenig Geschäftslogik und Speicher.
+0

+1, ive gota versuchen dieses Konzept ... Ich habe ähnliche Probleme –

1

Test count (myprimaryindexcolumn) und vergleichen Sie die Leistung Ihre Zahl (*)

2

Ich sehe, dass einige Personen gefragt haben, welche Suchmaschine Sie für die Abfrage verwendet haben. Ich würde Ihnen wärmstens empfehlen, MyISAM für die folgenden Probleme zu verwenden:

InnoDB - @Sorin Mocanu richtig erkannt, dass Sie einen vollständigen Tabellenscan unabhängig von Indizes durchführen werden.

MyISAM - hält die aktuelle Zeilenzahl immer griffbereit.

Schließlich ist, wie @justin angegeben, stellen Sie sicher, dass Sie die richtige Abdeckung Index:

CREATE INDEX ix_temp ON relations (relation_title, object_title); 
+4

FYI, der große Geschwindigkeitsvorteil von MyISAM for COUNT (*) Abfragen gilt nur, wenn Sie Zeilen in der gesamten Tabelle zählen. Wenn es eine WHERE-Klausel gibt, berechnen sowohl MyISAM als auch InnoDB die Anzahl, indem Zeilen im Index gezählt werden. Weitere Informationen finden Sie unter http://www.mysqlperformanceblog.com/2006/12/01/count-for-innodb-tables/. –

0

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

Verwandte Themen