2012-03-27 2 views
0

У меня возникла проблема с запросом и масштабирование этого запроса для работы с пользователями с большим количеством друзей. Цель запроса захватить топ «деятельность в исполнении ваших друзей в течение последних 30 дней Вот мой запрос:.MySQL Optimization for Social Friends Graph (Group By Friends)

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 

Этот запрос не принимает, как 25 секунд для запуска для всех пользователей, независимо от того, насколько большой или маленьких их друзей график Индексы ниже:.

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] 

Любая помощь будет принята с благодарностью

UPDATE:. Вот объясните

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

Возможно, выполнить EXPLAIN [Query], чтобы мы могли видеть, что делает планировщик запросов? –

+0

Plz добавить результат объяснения, чтобы получить, как двигатель извлекает данные –

+0

Я добавил объяснение выше. Не много строк для меня - но для запроса требуется более 25 секунд. – gregavola

ответ

2

Робин верен в поле даты. Если вы используете функцию, она должна будет вычислить, что для любого количества записей, на которые выполняется сканирование. То, как я это использую, использует переменные MySQL. Я вычисляю его ONCE в @StartDate и использую значение THAT для предложения join.

Единственная дополнительная вещь, которую я изменил, заключалась в добавлении предложения «STRAIGHT_JOIN». Во многих случаях я обнаружил, что это помогло мне и другим пользователям оптимизировать запрос. Это мешает MySQL пытаться интерпретировать запрос по-другому, возможно, сначала взглянув на таблицу действий, так как ее файл меньше, а затем обратно связан с ним. «STRAIGHT_JOIN» сообщает оптимизатору сделать это в указанном вами порядке.

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 обратной

В таком случае, и с этой «прокатке 30 дней назад» цикл, я бы тогда прибегнуть к созданию ночной таблицы, не что иное, как создание по идентификатору пользователя, активность и посчитать и запрос от вместо ...

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 

Убедитесь, что вы быть_наст индекс по этой ежедневной агрегатной таблице на (ID пользователя и общее количество), то запрос непосредственно к этому на основе друг ID по заказу TOTAL_COUNT d escending и limit 5. Небольшая цена, которую нужно заплатить за запуск ночного триггера/события/скрипта для создания этого ONCE. Насколько важно видеть активность для текущей даты. Является ли деятельность, которая радикальна в том, что один день активности будет искажать то, что вы в противном случае хотели бы представить пользователю?

+0

Спасибо за вашу помощь, но у меня все еще есть проблемы, когда учетная запись пользователя большая (имеет массу друзей). Он работает довольно долго для пользователей с более чем 800 друзьями, быстрыми для маленьких парней. Я думаю, если я не могу получить запрос для запуска менее чем за 2 секунды - есть ли способ правильно деномализовать данные, чтобы заставить это работать быстро? Я боюсь, что это проблема масштабирования. – gregavola

+0

@gregavola, см. Пересмотренный ответ. – DRapp

+0

Текущая дата не является критичной. Мой страх в том, что активность за последние 30 дней значительно возрастет, прямо сейчас мы говорим о ночной работе с 600 тыс. Строк, но завтра может быть 1 мм. Является ли это решение масштабируемым? Я просто выполнил запрос выше - и потребовалось 15 секунд, чтобы вернуть значения. – gregavola

0

Похоже, что это время для денормализации.

Если вы сохраняете только одну степень разделения, это довольно легко. Запишите «активность друга» для каждого из друзей на момент совершения операции. Он будет распространять нагрузку на запрос лица, осуществляющего деятельность.

Имейте это в виду - после того, как происходит действие, нет возможности «не принять» (хотя вы можете удалить его запись из фида). Это позволяет вам использовать более транзакционный подход, основанный на регистрации, для достижения производительности.

+0

Итак, вы говорите, что если у пользователя есть 15 000 друзей, мне нужно сделать вставку в БД 15 000 раз для каждого друга? Это не кажется мне оптимизацией, но я могу ошибаться? – gregavola

+0

Для регистрации активности на графике ... ну, да. Это будет намного лучше, чем присоединяется. И, реальный сценарий, сколько у ваших пользователей друзей 15K? Вы как преждевременно оптимизируете, так и пост-зрелую оптимизацию; выберите один;) – landons

+0

Кроме того, не пугайтесь заполняемой таблицы. MySQL неплохо справляется с этим. Вы можете разбить его для оптимизации для наиболее часто используемых данных (т. Е. Активность на прошлой неделе будет прочитана гораздо больше, чем два года назад). – landons

0

Начало, пытаясь изменить запрос к этому:

$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 

Основном фильтрует user_id и is_approved до вступления в другие таблицы. И лучше сгенерировать эту дату в php (или любом другом языке) и использовать это значение в MySQL, чтобы позволить MySQL вычислить ту же самую вещь (может быть, тысячи раз).

+0

I dont думаю, что tat будет работать, поскольку a.user_id не определен. – gregavola