2012-03-27 3 views
0

Ich habe ein bisschen ein Problem mit einer Abfrage und Skalierung dieser Abfrage für die Leistung mit Benutzern mit einer hohen Anzahl von Freunden. Das Ziel der Abfrage ist die Top „-Aktivitäten von deinen Freunden in den letzten 30 Tagen durchgeführt greifen Hier ist meine Frage:.MySQL-Optimierung für soziale Freunde Graph (Gruppe von Freunden)

SELECT a.activity_id, b.activity_name, count(a.activity_id) as total_count 
FROM friends as f 
INNER JOIN activities as a on (a.user_id = f.friend_id 
and a.created_at >= DATE_SUB(NOW(), INTERVAL 30 DAY) 
INNER JOIN activity as b on a.activity_id = b.activity_id 
WHERE f.user_id = 1 and f.is_approved = 1 
GROUP by a.activity_id 
ORDER by total_count DESC 
LIMIT 5 

Diese Abfrage dauert wie 25 Sekunden für alle Benutzer, egal wie groß oder klein laufen ihre Freunde graph ist Indizes sind unter:.

Table: activities 
PRIMARY: [act_id] Other: [activity_id, user_id], [user_id, created_at], [created_at] 

Table: friends 
PRIMARY: [user_id, friend_id] Other: [user_id, is_approved], [friend_id] 

Table: activity: 
PRIMARY: [activity_id] 

Jede Hilfe sehr geschätzt

UPDATE würde. Hier ist die erklären

id select_type  table key    key_len   ref    rows Extra 
1 SIMPLE F ref  friend_lookup 5 const,const  795  Using temporary; Using filesort 
1 SIMPLE A ref  user_id   4 F.friend_id  58  Using where 
1 SIMPLE B  eq_ref PRIMARY   4 P.activty_id 1  Using where 
+0

Vielleicht führen Sie eine EXPLAIN [Abfrage], so dass wir sehen können, was der Abfrageplaner macht? –

+0

Plz fügen Sie das EXPLAIN-Ergebnis hinzu, um zu bekommen, wie der Motor die Daten holt –

+0

Ich habe die Erklärung oben hinzugefügt. Nicht viele Zeilen für mich - aber dauert mehr als 25 Sekunden abzufragen. – gregavola

Antwort

2

Robin ist korrekt im Datumsfeld. Wenn Sie eine Funktion verwenden, muss diese für so viele Einträge berechnet werden, für die sie gescannt wird. Die Art, wie ich es unten verwende, verwendet MySQL-Variablen. Ich berechne es ONCE in ein @StartDate und verwende den THAT-Wert für die Join-Klausel.

Die einzige zusätzliche Sache, die ich änderte, war das Hinzufügen der "STRAIGHT_JOIN" -Klausel. In vielen Fällen habe ich festgestellt, dass es mir und anderen geholfen hat, die Abfrage zu optimieren. Es verhindert, dass MySQL versucht, die Abfrage auf eine andere Weise zu interpretieren, indem möglicherweise zuerst die Aktivitätstabelle betrachtet wird, da es sich um eine kleinere Datei handelt, und dann von dieser zurückverlinkt wird. "STRAIGHT_JOIN" teilt dem Optimierer mit, dies in der angegebenen Reihenfolge zu tun.

SELECT STRAIGHT_JOIN 
     a.activity_id, 
     b.activity_name, 
     count(a.activity_id) as total_count 
    FROM 
     (select @StartDate := date_Sub(now(), interval 30 day) sqlvars, 
     friends as f 
     INNER JOIN activities as a 
      on a.user_id = f.friend_id 
      and a.created_at >= @StartDate 
     INNER JOIN activity as b 
      on a.activity_id = b.activity_id 
    WHERE 
      f.user_id = 1 
     and f.is_approved = 1 
    GROUP by 
     a.activity_id 
    ORDER by 
     total_count DESC 
    LIMIT 5 

Per Feedback

Wenn das der Fall ist, und mit diesem „30 Tagen rollt vor“ -Zyklus, dann würde ich auf eine nächtliche Tabellenerstellung zurückgreifen, die von Benutzer-ID nichts anderes als eine Schöpfung ist, Aktivität und zählen und Abfrage von dieser Stelle ...

create table DailyRollupActivity 
select a.user_id, 
     a.activity_id, 
     count(*) total_count 
    from 
     (select @StartDate := date_Sub(now(), interval 30 day) sqlvars, 
     Activities a 
    where 
     a.created_at >= @StartDate 
    group by 
     a.User_ID, 
     a.Activity_ID 

Stellen Sie sicher, Sie auf dieser aggregierten täglichen Tabelle durch die (User-ID und der Gesamtzahl), einen Index haben dann diese direkt auf dem Freund ID Abfrage von TOTAL_COUNT bestellt d escending und limit 5. Kleiner zu zahlender Preis, um ein nächtliches Trigger/Ereignis/Skript zu haben, das ausgeführt wird, um dieses ONCE zu erstellen. Wie wichtig ist es, die Aktivität auch für das aktuelle Datum zu sehen. Ist die Aktivität, die diese eintägige Aktivität einschneidend macht, etwas, das Sie dem Benutzer sonst noch präsentieren möchten?

+0

Vielen Dank für Ihre Hilfe, aber ich habe immer noch Probleme, wenn das Benutzerkonto groß ist (hat Tonnen von Freunden). Es läuft ziemlich lange für Benutzer mit 800+ Freunden, schnell für die kleineren Jungs. Ich denke, wenn ich keine Anfrage bekomme, um in weniger als 2 Sekunden zu laufen - gibt es eine Möglichkeit, die Daten korrekt zu benennen, um das schnell zu machen? Ich befürchte dies ein Skalierungsproblem. – gregavola

+0

@gregavola, siehe überarbeitete Antwort. – DRapp

+0

Das aktuelle Datum ist nicht kritisch. Ich habe die Befürchtung, dass die Aktivität in den letzten 30 Tagen deutlich zunehmen wird. Momentan sprechen wir über einen nächtlichen Job mit 600.000 Zeilen, aber morgen könnte es 1mm sein. Ist diese Lösung skalierbar? Ich habe gerade die Abfrage oben ausgeführt - und es dauerte 15 Sekunden, um Werte zurückzugeben. – gregavola

0

Es scheint, als wäre es an der Zeit, ein wenig zu denormalisieren.

Wenn Sie nur einen Grad der Trennung speichern, ist dies ziemlich einfach. Loggen Sie die "Freundschaftsaktivität" für jeden der Freunde zum Zeitpunkt der Aktivität ein. Es verteilt die Last auf die Anfrage der Person, die die Aktivität ausführt.

Behalten Sie dies im Hinterkopf - nachdem eine Aktivität stattfindet, gibt es keine Möglichkeit, dass sie "nicht stattfindet" (obwohl Sie den Datensatz möglicherweise aus einem Feed löschen). Dies ermöglicht Ihnen eine eher transaktionale Protokollierung, um der Leistung gerecht zu werden.

+0

Also mein Sprichwort, dass, wenn ein Benutzer 15.000 Freunde hat, ich für jeden Freund 15.000 mal einen Eintrag in die DB machen muss? Das erscheint mir nicht als Optimierung - aber ich könnte falsch liegen? – gregavola

+0

Für ein Aktivitätsprotokoll in einem Diagramm ... nun ja. Das würde viel besser skalieren als Joins. Und wie viele Ihrer Nutzer haben 15K Freunde? Sie optimieren beide vorzeitig und optimieren nach einer gewissen Zeit; Wählen Sie eine;) – landons

+0

Darüber hinaus haben Sie keine Angst vor einem Tisch zu füllen. MySQL ist ziemlich gut darin, das zu handhaben. Sie können sie partitionieren, um sie für die häufiger verwendeten Daten zu optimieren (d. H. Die Aktivität in der letzten Woche wird weit mehr als zwei Jahre zurückliegen). – landons

0

Start, indem Sie versuchen, die Abfrage zu, dies zu ändern:

$str_date = date('Y-m-d H:i:s', strtotime('today -30 Days')); 

SELECT a.activity_id, b.activity_name, count(a.activity_id) as total_count 
FROM ( SELECT friend_id 
     FROM friends 
     WHERE user_id = 1 and is_approved = 1) as f 
INNER JOIN ( SELECT user_id, activity_id 
       FROM activities 
       WHERE created_at >= {$str_date}) as a 
on a.user_id = f.friend_id 
INNER JOIN activity as b on a.activity_id = b.activity_id 
GROUP by a.activity_id 
ORDER by total_count DESC 
LIMIT 5 

Basicly filtert user_id und is_approved bevor die anderen Tabellen verknüpft. Und es ist besser, dieses Datum in PHP (oder in welcher Sprache) zu generieren und diesen Wert in MySQL zu verwenden, um dann MySQL genau dasselbe (vielleicht tausende Male) berechnen zu lassen.

+0

I dont think will tat funktioniert, da a.user_id nicht definiert ist. – gregavola

Verwandte Themen