2009-07-29 19 views
2

Ich verwende die folgende MySQL-Abfrage in einem PHP-Skript für eine Datenbank, die über 300.000.000 (ja, dreihundert Millionen) Zeilen enthält. Ich weiß, dass es sehr ressourcenintensiv ist und es ewig dauert, diese eine Abfrage auszuführen. Weiß jemand, wie ich entweder die Abfrage optimieren kann oder die Informationen auf andere Weise schneller bekommen kann?Wie kann ich diese MySQL-Abfrage optimieren?

Ich muss in der Lage sein, eine ganze Zahl zwischen 1 und 15 anstelle der 14 in MID() zu verwenden. Ich muss auch in der LIKE-Klausel Zeichenfolgenlängen innerhalb des gleichen Bereichs übereinstimmen können.

Tabelle Info:

games | longint, unsigned, Primary Key 
win | bit(1) 
loss | bit(1)

Beispiel Abfrage:

SELECT MID(`game`,14,1) AS `move`, 
     COUNT(*) AS `games`, 
     SUM(`win`) AS `wins`, 
     SUM(`loss`) AS `losses` 
FROM `games` 
WHERE `game` LIKE '1112223334%' 
GROUP BY MID(`game`,1,14)

Vielen Dank im Voraus für Ihre Hilfe!

Antwort

5

Erstens haben einen Index auf dem Spielfeld ... :)

Die Abfrage einfach und unkompliziert scheint, aber es verbirgt sich die Tatsache, dass ein datasbase Designänderung ist wahrscheinlich erforderlich.

In solchen Fällen bevorzuge ich immer ein Feld, das aggregierte Daten enthält, entweder pro Tag, pro Benutzer oder für jede andere Achse. Auf diese Weise können Sie eine tägliche Aufgabe haben, die die relevanten Daten aggregiert und in der Datenbank speichert.

Wenn Sie diese Abfrage tatsächlich oft aufrufen, sollten Sie das Prinzip der Verringerung der Effizienz der Insertion verwenden, um die Effizienz der Suche zu erhöhen.

1

Die Abfrage ist einfach und abgesehen davon, sicherzustellen, dass es alle notwendigen Indizes ("Spielfeld" offensichtlich) gibt, gibt es möglicherweise keinen offensichtlichen Weg, um es schneller zu machen, indem Sie nur die Abfrage neu schreiben. Einige Modifikationen von Datenstrukturen werden wahrscheinlich notwendig sein.

Ein Weg: die Summen vorberechnen. Jeder dieser Datensätze hat höchstwahrscheinlich ein create_date oder ein automatisch inkrementiertes Schlüsselfeld. Berechne die Summen für alle Datensätze, wobei dieses Feld ≤ ein X ist, lege Ergebnisse in eine Seitentabelle und dann musst du nur für alle Datensätze> X berechnen und dann diese Teilergebnisse mit deinen vorberechneten zusammenfassen.

2

Es sieht aus wie die game Spalte speichert zwei (oder möglicherweise mehr) verschiedene Dinge, dass diese Abfrage verwendet:

  1. Filterung nach Beginn der game (die ersten 10 Zeichen)
  2. Gruppierung nach und Rückkehr MID( Spiel ,1,14) (I einer der MID Ausdrücke gehe davon ist ein Tippfehler.

ich, dass oben aufgespalten würde, so dass Sie nicht verwenden Zeichenfolge operat Ionen auf der game Spalte, und auch Indizes auf die neuen Spalten, so dass Sie sie richtig filtern und gruppieren können.

Diese Abfrage führt eine Menge Konvertierungen (long to string) und Stringmanipulationen durch, die nicht notwendig wären, wenn die Tabelle normalisiert wäre (wie in einer Information pro Spalte anstelle von mehreren wie jetzt).

Lassen Sie die Spalte game so, wie sie ist, und erstellen Sie eine game_filter Stringspalte, die darauf basiert, um sie in Ihrer WHERE-Klausel zu verwenden. Richten Sie dann eine game_group Spalte ein und füllen Sie sie mit dem Ausdruck MID auf der Einfügung. Richten Sie diese beiden Spalten als Clustered-Index ein, zuerst game_filter, dann game_group.

0
SELECT MID(`game`,14,1) AS `move`, 
     COUNT(*) AS `games`, 
     SUM(`win`) AS `wins`, 
     SUM(`loss`) AS `losses` 
FROM `games` 
WHERE `game` LIKE '1112223334%' 

einen Index für game erstellen:

CREATE INDEX ix_games_game ON games (game) 

und schreiben Sie Ihre Abfrage wie folgt aus:

SELECT move, 
     (
     SELECT COUNT(*) 
     FROM games 
     WHERE game >= move 
       AND game < CONCAT(SUBSTRING(move, 1, 13), CHR(ASCII(SUBSTRING(move, 14, 1)) + 1)) 
     ), 
     (
     SELECT SUM(win) 
     FROM games 
     WHERE game >= move 
       AND game < CONCAT(SUBSTRING(move, 1, 13), CHR(ASCII(SUBSTRING(move, 14, 1)) + 1)) 
     ), 
     (
     SELECT SUM(lose) 
     FROM games 
     WHERE game >= move 
       AND game < CONCAT(SUBSTRING(move, 1, 13), CHR(ASCII(SUBSTRING(move, 14, 1)) + 1)) 
     ) 
FROM (
     SELECT DISTINCT SUBSTRING(q.game, 1, 14) AS move 
     FROM games 
     WHERE game LIKE '1112223334%' 
     ) q 

Damit wird der Index auf game effizienter nutzen helfen.

+0

Warum die GROUP BY-Klausel löschen? Er möchte, dass COUNT und SUM durch die 14. Stelle der Spielspalte geteilt werden. – mlarsen

+0

@mlarsen: Ich habe es nicht zuerst bekommen und die Antwort gelöscht. Jetzt ist alles neu geschrieben. – Quassnoi

1

Sie konnten die MID (game, 14,1) und MID (game, 1.14) und speichern Sie die ersten zehn Ziffern der game in einer separaten Spalte GameID vorauszuberechnen, die indiziert ist.

Es könnte auch eine Idee sein, zu untersuchen, ob Sie nur eine Aggregattabelle der vorberechneten Werte speichern könnten, sodass Sie die Spalte count und wins or losses stattdessen einfügen.

0

Können Sie das Ergebnis mit Memcache oder etwas Ähnlichem zwischenspeichern? Das würde bei wiederholten Treffern helfen. Selbst wenn Sie die Ergebnismenge nur für einige Sekunden zwischenspeichern, können Sie möglicherweise viele DB-Lesevorgänge vermeiden.