Wie beschleunige ich Abfragen in einer großen Tabelle mit 220 Millionen Zeilen (9 GB Daten)?

31

Das Thema:

Wir haben eine soziale Website, auf der Mitglieder sich gegenseitig auf Kompatibilität oder Übereinstimmung bewerten können. Diese user_match_ratingsTabelle enthält über 220 Millionen Zeilen (9 Gig-Daten oder fast 20 Gig-Indizes). Abfragen für diese Tabelle werden routinemäßig in slow.log angezeigt (Schwellwert> 2 Sekunden) und sind die am häufigsten protokollierte langsame Abfrage im System:

Query_time: 3  Lock_time: 0  Rows_sent: 3  Rows_examined: 1051
"select rating, count(*) as tally from user_match_ratings where rated_user_id = 395357 group by rating;"

Query_time: 4  Lock_time: 0  Rows_sent: 3  Rows_examined: 1294
"select rating, count(*) as tally from user_match_ratings where rated_user_id = 4182969 group by rating;"

Query_time: 3  Lock_time: 0  Rows_sent: 3  Rows_examined: 446
"select rating, count(*) as tally from user_match_ratings where rated_user_id = 630148 group by rating;"

Query_time: 5  Lock_time: 0  Rows_sent: 3  Rows_examined: 3788
"select rating, count(*) as tally from user_match_ratings where rated_user_id = 1835698 group by rating;"

Query_time: 17  Lock_time: 0  Rows_sent: 3  Rows_examined: 4311
"select rating, count(*) as tally from user_match_ratings where rated_user_id = 1269322 group by rating;"

MySQL-Version:

  • Protokollversion: 10
  • Version: 5.0.77-log
  • Version BDB: Sleepycat Software: Berkeley DB 4.1.24: (29. Januar 2009)
  • version compile machine: x86_64 version_compile_os: redhat-linux-gnu

Tischinfo:

SHOW COLUMNS FROM user_match_ratings;

Gibt:

╔═══════════════╦════════════╦════╦═════╦════════╦════════════════╗
 id             int(11)     NO  PRI  NULL    auto_increment 
 rater_user_id  int(11)     NO  MUL  NULL                   
 rated_user_id  int(11)     NO  MUL  NULL                   
 rating         varchar(1)  NO       NULL                   
 created_at     datetime    NO       NULL                   
╚═══════════════╩════════════╩════╩═════╩════════╩════════════════╝

Beispielabfrage:

select * from mutual_match_ratings where id=221673540;

gibt:

╔═══════════╦═══════════════╦═══════════════╦════════╦══════════════════════╗
 id         rater_user_id  rated_user_id  rating  created_at           
╠═══════════╬═══════════════╬═══════════════╬════════╬══════════════════════╣
 221673540  5699713        3890950        N       2013-04-09 13:00:38  
╚═══════════╩═══════════════╩═══════════════╩════════╩══════════════════════╝

Indizes

In der Tabelle sind 3 Indizes eingerichtet:

  1. einzelner Index auf rated_user_id
  2. zusammengesetzter Index auf rater_user_idundcreated_at
  3. zusammengesetzter Index auf rated_user_idundrater_user_id
zeige Index von user_match_ratings;

gibt:

╔════════════════════╦════════════╦═══════════════════════════╦══════════════╦═══════════════╦═══════════╦═════════════╦══════════╦════════╦═════════════════════════╦════════════╦══════════════════╗
 Table               Non_unique  Key_name                   Seq_in_index  Column_name    Collation  Cardinality  Sub_part  Packed  Null                     Index_type  Comment          
╠════════════════════╬════════════╬═══════════════════════════╬══════════════╬═══════════════╬═══════════╬═════════════╬══════════╬════════╬═════════════════════════╬════════════╬══════════════════╣
 user_match_ratings  0           PRIMARY                    1             id             A          220781193    NULL      NULL    BTREE                                                 
 user_match_ratings  1           user_match_ratings_index1  1             rater_user_id  A          11039059     NULL      NULL    BTREE                                                 
 user_match_ratings  1           user_match_ratings_index1  2             created_at     A          220781193    NULL      NULL    BTREE                                                 
 user_match_ratings  1           user_match_ratings_index2  1             rated_user_id  A          4014203      NULL      NULL    BTREE                                                 
 user_match_ratings  1           user_match_ratings_index2  2             rater_user_id  A          220781193    NULL      NULL    BTREE                                                 
 user_match_ratings  1           user_match_ratings_index3  1             rated_user_id  A          2480687      NULL      NULL    BTREE                                                 
╚════════════════════╩════════════╩═══════════════════════════╩══════════════╩═══════════════╩═══════════╩═════════════╩══════════╩════════╩═════════════════════════╩════════════╩══════════════════╝

Selbst mit den Indizes sind diese Abfragen langsam.

Meine Frage:

Würde das Trennen dieser Tabelle / Daten in eine andere Datenbank auf einem Server, der über genügend RAM verfügt, um diese Daten im Speicher zu speichern, diese Abfragen beschleunigen? Enthalten die Tabellen / Indizes irgendetwas, das wir verbessern können, um diese Abfragen zu beschleunigen?

Derzeit haben wir 16 GB Speicher; Wir möchten jedoch entweder die vorhandene Maschine auf 32 GB aufrüsten oder eine neue Maschine mit mindestens so vielen, möglicherweise auch Solid-State-Laufwerken hinzufügen.

Ranknoodle
quelle
1
Ihre Frage ist unglaublich. Ich bin sehr interessiert an Ihrer aktuellen Lösung, wie Sie es geschafft haben, das Ergebnis in <= 2 Sekunden zu erzielen? Weil ich eine Tabelle habe, die nur 20 Millionen Datensätze enthält und trotzdem 30 Sekunden dauert SELECT QUERY. Würden Sie bitte vorschlagen? PS Ihre Frage hat mich gezwungen, dieser Community beizutreten (y);)
NullPointer
2
Schauen Sie sich die Indizes der Tabelle an, die Sie abfragen. Oft können viele Verbesserungen an Abfragen vorgenommen werden, indem der entsprechende Index erstellt wird. Nicht immer, aber es gibt viele Fälle, in denen Abfragen schnell ausgeführt werden, indem ein Index für die Spalten in der where-Klausel einer Abfrage bereitgestellt wird. Vor allem, wenn ein Tisch immer größer wird.
Ranknoodle
Sicher @Ranknoodle. Vielen Dank. Ich werde das jeweils überprüfen.
NullPointer

Antworten:

28

Gedanken zum Thema, in zufälliger Reihenfolge geworfen:

  • Der offensichtliche Index für diese Abfrage ist: (rated_user_id, rating). Eine Abfrage, die Daten für nur einen der Millionen Benutzer abruft und 17 Sekunden benötigt, führt zu einem Fehler: Aus dem (rated_user_id, rater_user_id)Index lesen und dann aus der Tabelle die (Hundert- bis Tausend-) Werte für die ratingSpalte lesen , wie ratinges in keinem Index der Fall ist. Die Abfrage muss also viele Zeilen der Tabelle lesen, die sich an vielen verschiedenen Speicherorten befinden.

  • Versuchen Sie vor dem Hinzufügen zahlreicher Indizes zu den Tabellen, die Leistung der gesamten Datenbank, die Gesamtheit der langsamen Abfragen zu analysieren, und überprüfen Sie erneut die Auswahl der Datentypen, die von Ihnen verwendete Engine und die Konfigurationseinstellungen.

  • Ziehen Sie die Umstellung auf eine neuere Version von MySQL, 5.1, 5.5 oder sogar 5.6 in Betracht (auch: Percona und MariaDB-Versionen). Mehrere Vorteile wurden behoben, der Optimierer wurde verbessert und Sie können den unteren Schwellenwert für langsame Abfragen auf weniger als 1 Sekunde festlegen (wie 10 Millisekunden). Dadurch erhalten Sie weitaus bessere Informationen zu langsamen Abfragen.

  • Die Wahl für den Datentyp von ratingist komisch. VARCHAR(1)? Warum nicht CHAR(1)? Warum nicht TINYINT? Dies spart Platz, sowohl in der Tabelle als auch in den Indizes, die diese Spalte enthalten (werden). Eine varchar (1) -Spalte benötigt ein Byte mehr als char (1), und wenn es sich um utf8 handelt, benötigen die (var) char -Spalten 3 (oder 4) Byte anstelle von 1 (tinyint).

ypercubeᵀᴹ
quelle
2
Wie viel Leistungseinbußen oder Speicherverluste in Prozent, wenn Sie den falschen Datentyp verwenden?
FlyingAtom
1
@FlyingAtom Dies hängt vom Einzelfall ab. Bei einigen indizierten Spalten, die noch gescannt werden müssen (z. B. wenn Sie keine where-Klausel haben, aber nur diese Spalte abrufen), entscheidet sich das Modul möglicherweise für das Durchsuchen des Index anstelle von Wenn Sie Ihren Datentyp auf die Hälfte der Größe optimieren, ist der Scan doppelt so schnell und die Antwort halb so groß. Wenn Sie weiterhin die Tabelle anstelle eines Index durchsuchen (z. B. wenn Sie nicht nur die im Index enthaltenen Spalten abrufen), sind die Vorteile geringer.
Sebastián Grignoli
-1

Ich habe Tische für die Bundesregierung mit teilweise 60 Millionen Platten bearbeitet.

Wir hatten viele dieser Tische.

Und wir mussten viele Male die Gesamtanzahl der Zeilen aus einer Tabelle kennen.

Nach einem Gespräch mit Oracle- und Microsoft-Programmierern waren wir nicht so glücklich ...

Deshalb haben wir, die Gruppe der Datenbankprogrammierer, entschieden, dass in jeder Tabelle immer der Datensatz ist, in dem die gesamten Datensatznummern gespeichert sind. Wir haben diese Nummer abhängig von INSERT- oder DELETE-Zeilen aktualisiert.

Wir haben alle anderen Möglichkeiten ausprobiert. Dies ist bei weitem der schnellste Weg.

Wir verwenden diesen Weg jetzt seit 1998 und hatten in all unseren Multi-Millionen-Datensatztabellen nie eine falsche Anzahl von Zeilen.

FrankyBkk
quelle
7
Ich würde vorschlagen, sich einige der Funktionen anzusehen, die in den letzten 18 Jahren eingeführt wurden. Unter anderem count(*)hat einige Verbesserungen.
Dezso
Woher weißt du, dass du nie eine falsche Nummer hattest, wenn du sie nicht zählen konntest? uhmmmm ...
Tonca
-3

Ich werde versuchen, nach Bewertungstypen zu partitionieren, wie zum Beispiel:

gegenseitige_Match_Ratings_N, gegenseitige_Match_Ratings_S, etc.

Sie sollten für jeden Typ eine Abfrage durchführen, dies ist jedoch möglicherweise schneller als auf die andere Weise. Versuche es.

Dies setzt voraus, dass Sie eine feste Anzahl von Bewertungstypen haben und diese Tabelle nicht für andere Abfragen benötigen, die mit dieser neuen Struktur am schlimmsten wären.

In diesem Fall sollten Sie nach einem anderen Ansatz suchen oder zwei Kopien der Tabelle (Ihre ursprüngliche Tabelle und partitionierte) verwalten, wenn dies in Bezug auf Speicherplatz und Wartbarkeit (oder Anwendungslogik) erschwinglich ist.

appartisan
quelle