Ich habe eine Abfrage, deren Ausführung besonders lange dauert (15+ Sekunden) und die mit der Zeit immer schlimmer wird, wenn mein Datensatz wächst. Ich habe dies in der Vergangenheit optimiert und Indizes, Sortierung auf Codeebene und andere Optimierungen hinzugefügt, aber es muss noch weiter verfeinert werden.
SELECT sounds.*, avg(ratings.rating) AS avg_rating, count(ratings.rating) AS votes FROM `sounds`
INNER JOIN ratings ON sounds.id = ratings.rateable_id
WHERE (ratings.rateable_type = 'Sound'
AND sounds.blacklisted = false
AND sounds.ready_for_deployment = true
AND sounds.deployed = true
AND sounds.type = "Sound"
AND sounds.created_at > "2011-03-26 21:25:49")
GROUP BY ratings.rateable_id
Der Zweck der Abfrage ist es, mir die sound id
und die durchschnittliche Bewertung der neuesten, veröffentlichten Sounds zu geben. Es gibt ungefähr 1500 Sounds und 2 Millionen Bewertungen.
Ich habe mehrere Indizes sounds
mysql> show index from sounds;
+--------+------------+------------------------------------------+--------------+----------------------+-----------+-------------+----------+--------+------+------------+————+
| Table | Non_unique | Key_name | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment |
+--------+------------+------------------------------------------+--------------+----------------------+-----------+-------------+----------+--------+------+------------+————+
| sounds | 0 | PRIMARY | 1 | id | A | 1388 | NULL | NULL | | BTREE | |
| sounds | 1 | sounds_ready_for_deployment_and_deployed | 1 | deployed | A | 5 | NULL | NULL | YES | BTREE | |
| sounds | 1 | sounds_ready_for_deployment_and_deployed | 2 | ready_for_deployment | A | 12 | NULL | NULL | YES | BTREE | |
| sounds | 1 | sounds_name | 1 | name | A | 1388 | NULL | NULL | | BTREE | |
| sounds | 1 | sounds_description | 1 | description | A | 1388 | 128 | NULL | YES | BTREE | |
+--------+------------+------------------------------------------+--------------+----------------------+-----------+-------------+----------+--------+------+------------+---------+
und mehrere auf ratings
mysql> show index from ratings;
+---------+------------+-----------------------------------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+————+
| Table | Non_unique | Key_name | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment |
+---------+------------+-----------------------------------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+————+
| ratings | 0 | PRIMARY | 1 | id | A | 2008251 | NULL | NULL | | BTREE | |
| ratings | 1 | index_ratings_on_rateable_id_and_rating | 1 | rateable_id | A | 18 | NULL | NULL | | BTREE | |
| ratings | 1 | index_ratings_on_rateable_id_and_rating | 2 | rating | A | 9297 | NULL | NULL | YES | BTREE | |
+---------+------------+-----------------------------------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+
Hier ist der EXPLAIN
mysql> EXPLAIN SELECT sounds.*, avg(ratings.rating) AS avg_rating, count(ratings.rating) AS votes FROM sounds INNER JOIN ratings ON sounds.id = ratings.rateable_id WHERE (ratings.rateable_type = 'Sound' AND sounds.blacklisted = false AND sounds.ready_for_deployment = true AND sounds.deployed = true AND sounds.type = "Sound" AND sounds.created_at > "2011-03-26 21:25:49") GROUP BY ratings.rateable_id;
+----+-------------+---------+--------+--------------------------------------------------+-----------------------------------------+---------+-----------------------------------------+---------+——————+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+---------+--------+--------------------------------------------------+-----------------------------------------+---------+-----------------------------------------+---------+——————+
| 1 | SIMPLE | ratings | index | index_ratings_on_rateable_id_and_rating | index_ratings_on_rateable_id_and_rating | 9 | NULL | 2008306 | Using where |
| 1 | SIMPLE | sounds | eq_ref | PRIMARY,sounds_ready_for_deployment_and_deployed | PRIMARY | 4 | redacted_production.ratings.rateable_id | 1 | Using where |
+----+-------------+---------+--------+--------------------------------------------------+-----------------------------------------+---------+-----------------------------------------+---------+-------------+
Ich speichere die einmal erhaltenen Ergebnisse im Cache, daher ist die Leistung der Website kein großes Problem, aber die Ausführung meiner Cache-Wärmer dauert immer länger, da dieser Aufruf so lange dauert, und das wird langsam zu einem Problem. Dies scheint nicht viele Zahlen zu sein, die in einer Abfrage zusammengefasst werden müssen…
Was kann ich noch tun, um die Leistung zu verbessern ?
quelle
EXPLAIN
Ausgabe zeigen ?EXPLAIN SELECT sounds.*, avg(ratings.rating) AS avg_rating, count(ratings.rating) AS votes FROM sounds INNER JOIN ratings ON sounds.id = ratings.rateable_id WHERE (ratings.rateable_type = 'Sound' AND sounds.blacklisted = false AND sounds.ready_for_deployment = true AND sounds.deployed = true AND sounds.type = "Sound" AND sounds.created_at > "2011-03-26 21:25:49") GROUP BY ratings.rateable_id
Antworten:
Nachdem Sie sich die Abfrage, die Tabellen und die WHERE AND GROUP BY-Klauseln angesehen haben, empfehle ich Folgendes:
Empfehlung 1) Refaktorieren Sie die Abfrage
Ich habe die Abfrage neu organisiert, um drei (3) Dinge zu tun:
Hier ist meine vorgeschlagene Abfrage:
Empfehlung Nr. 2) Indizieren Sie die Sounds-Tabelle mit einem Index, der die WHERE-Klausel enthält
Die Spalten dieses Index enthalten alle Spalten aus der WHERE-Klausel, wobei die statischen Werte zuerst und das Ziel zuletzt verschoben werden
Ich glaube aufrichtig, dass Sie angenehm überrascht sein werden. Versuche es !!!
UPDATE 2011-05-21 19:04
Ich habe gerade die Kardinalität gesehen. Autsch !!! Kardinalität von 1 für rateable_id. Junge, ich fühle mich dumm !!!
UPDATE 2011-05-21 19:20
Vielleicht reicht es aus, den Index zu erstellen, um die Dinge zu verbessern.
UPDATE 2011-05-21 22:56
Bitte führen Sie dies aus:
UPDATE 2011-05-21 23:34
Ich habe es wieder überarbeitet. Versuchen Sie dies bitte:
UPDATE 2011-05-21 23:55
Ich habe es wieder überarbeitet. Versuchen Sie dies bitte (letztes Mal):
UPDATE 2011-05-22 00:12
Ich hasse es aufzugeben !!!!
UPDATE 2011-05-22 07:51
Es hat mich gestört, dass die Bewertungen mit 2 Millionen Zeilen in der EXPLAIN zurückkommen. Dann traf es mich. Möglicherweise benötigen Sie einen anderen Index in der Bewertungstabelle, der mit rateable_type beginnt:
Das Ziel dieses Index ist es, die temporäre Tabelle, die die Bewertungen manipuliert, so zu reduzieren, dass sie weniger als 2 Millionen beträgt. Wenn wir diese temporäre Tabelle deutlich kleiner machen können (mindestens die Hälfte), können wir eine bessere Hoffnung auf Ihre und meine Abfrage haben, die auch schneller funktioniert.
Nachdem Sie diesen Index erstellt haben, wiederholen Sie bitte meine ursprünglich vorgeschlagene Abfrage und versuchen Sie auch Ihre:
UPDATE 2011-05-22 18:39: SCHLUSSWÖRTER
Ich hatte eine Abfrage in einer gespeicherten Prozedur überarbeitet und einen Index hinzugefügt, um eine Frage zur Beschleunigung zu beantworten. Ich bekam 6 positive Stimmen, ließ die Antwort akzeptieren und nahm ein Kopfgeld von 200 auf.
Ich hatte auch eine andere Abfrage überarbeitet (marginale Ergebnisse) und einen Index hinzugefügt (dramatische Ergebnisse). Ich bekam 2 positive Stimmen und hatte die Antwort akzeptiert.
Ich habe einen Index für eine weitere Abfrage-Herausforderung hinzugefügt und wurde einmal hochgestuft
und jetzt deine Frage .
Der Wunsch, alle Fragen wie diese (einschließlich Ihrer) zu beantworten, wurde von einem YouTube-Video inspiriert, das ich mir beim Refactoring von Abfragen angesehen habe.
Nochmals vielen Dank, @coneybeare !!! Ich wollte diese Frage so weit wie möglich beantworten und nicht nur Punkte oder Auszeichnungen akzeptieren. Jetzt kann ich fühlen, dass ich die Punkte verdient habe !!!
quelle
Danke für die EXPLAIN-Ausgabe. Wie Sie dieser Aussage entnehmen können, dauert der vollständige Tabellenscan in der Bewertungstabelle so lange. Nichts in der WHERE-Anweisung filtert die 2 Millionen Zeilen nach unten.
Sie könnten einen Index für reviews.type hinzufügen, aber ich vermute, dass die KARDINALITÄT sehr niedrig sein wird und Sie immer noch einige Zeilen weiter scannen werden
ratings
.Alternativ können Sie versuchen , verwenden Index Hinweise auf Kraft mysql die Klänge Indizes zu verwenden.
Aktualisiert:
Wenn ich es wäre, würde ich einen Index hinzufügen,
sounds.created
da dies die beste Chance hat, die Zeilen zu filtern, und wahrscheinlich den MySQL-Abfrageoptimierer zwingen wird, die Sounds-Tabellenindizes zu verwenden. Achten Sie nur auf Abfragen, die lange erstellte Zeitrahmen verwenden (1 Jahr, 3 Monate, hängt nur von der Größe der Soundtabelle ab).quelle
Wenn dies eine "on-the-fly" verfügbare Abfrage sein muss, schränkt dies Ihre Optionen ein wenig ein.
Ich werde vorschlagen, dieses Problem zu teilen und zu erobern.
quelle
sounds
,ratings
in der Mitte Abfrage), aber es gesperrt meine SQL - Box und ich hatte den Prozess zu töten.Verwenden Sie JOINs, keine Unterabfragen. Hat einer Ihrer Unterabfrageversuche geholfen?
SHOW CREATE TABLE klingt \ G.
SHOW CREATE TABLE Bewertungen \ G.
Oft ist es vorteilhaft, "zusammengesetzte" Indizes zu haben, keine einspaltigen. Vielleicht INDEX (Typ, created_at)
Sie filtern nach beiden Tabellen in einem JOIN. Das ist wahrscheinlich ein Leistungsproblem.
Empfehlen Sie, dass Sie eine auto_increment-ID aktiviert haben
ratings
, erstellen Sie eine Übersichtstabelle und verwenden Sie die AI-ID, um zu verfolgen, wo Sie " aufgehört " haben. Speichern Sie jedoch keine Durchschnittswerte in einer Übersichtstabelle:Behalten Sie stattdessen die SUMME (reviews.rating) bei. Der Durchschnitt der Durchschnittswerte ist für die Berechnung eines Durchschnitts mathematisch falsch. (Summe der Summen) / (Summe der Zählungen) ist korrekt.
quelle