Лучший способ обновить рейтинг пользователей, не убивая сервер - PullRequest
3 голосов
/ 04 июня 2009

У меня есть веб-сайт, в котором рейтинг пользователей занимает центральное место, но количество пользователей выросло до более чем 50 000, и это заставляет сервер перебирать все эти данные, чтобы обновлять рейтинг каждые 5 минут. Есть ли лучший метод, который можно использовать для простого обновления рангов по крайней мере каждые 5 минут? Это не обязательно должно быть с php, это может быть что-то, что запускается как скрипт perl, или что-то подобное, если что-то подобное сможет сделать работу лучше (хотя я не уверен, почему это так, просто оставив свой варианты открываются здесь).

Это то, что я сейчас делаю для обновления рангов:

$get_users = mysql_query("SELECT id FROM users WHERE status = '1' ORDER BY month_score DESC");
$i=0;
while ($a = mysql_fetch_array($get_users)) {
    $i++;
    mysql_query("UPDATE users SET month_rank = '$i' WHERE id = '$a[id]'");
}

ОБНОВЛЕНИЕ (решение):

Вот код решения, для выполнения и обновления всех 50 000 строк которого требуется менее половины секунды (сделайте ранжирование первичным ключом, как предложено Томом Хейем).

mysql_query("TRUNCATE TABLE userRanks");
mysql_query("INSERT INTO userRanks (userid) SELECT id FROM users WHERE status = '1' ORDER BY month_score DESC");
mysql_query("UPDATE users, userRanks SET users.month_rank = userRanks.rank WHERE users.id = userRanks.id");

Ответы [ 8 ]

8 голосов
/ 04 июня 2009

Сделать userRanks.rank автоинкрементным первичным ключом. Если вы затем вставите идентификаторы пользователей в userRanks в порядке убывания, это увеличит столбец rank в каждой строке. Это должно быть очень быстро.

TRUNCATE TABLE userRanks;
INSERT INTO userRanks (userid) SELECT id FROM users WHERE status = '1' ORDER BY month_score DESC;
UPDATE users, userRanks SET users.month_rank = userRanks.rank WHERE users.id = userRanks.id;
3 голосов
/ 04 июня 2009

Простая альтернатива массовому обновлению может выглядеть примерно так:

set @rnk = 0;
update users 
set month_rank = (@rnk := @rnk + 1)
order by month_score DESC

В этом коде используется локальная переменная (@rnk), которая увеличивается при каждом обновлении. Поскольку обновление выполняется для упорядоченного списка строк, столбцу month_rank будет присвоено увеличенное значение для каждой строки.

3 голосов
/ 04 июня 2009

Мой первый вопрос: почему вы выполняете эту операцию типа опроса каждые пять минут?

Конечно, ранжирование изменений будет происходить в ответ на какое-то событие, и вы можете локализовать изменения в нескольких строках в базе данных в тот момент, когда это событие произойдет . Я почти уверен, что вся 50000 пользователей не меняет рейтинг каждые пять минут.

Я предполагаю, что "status = '1'" указывает, что ранг пользователя изменился, поэтому вместо того, чтобы устанавливать его, когда пользователь инициирует изменение ранга, почему бы вам не рассчитать ранг в это время?

Казалось бы, это лучшее решение, поскольку стоимость переоценки будет амортизироваться по всем операциям.

Теперь я, возможно, неправильно понял, что вы имели в виду под рейтингом, и в этом случае не стесняйтесь меня поправлять.

1 голос
/ 05 июня 2009

Вы можете разделить обработку рейтинга и выполнение обновления. Итак, просмотрите все данные и обработайте запрос. Добавьте каждый оператор обновления в кеш. Когда обработка будет завершена, запустите обновления. У вас должна быть часть WHERE UPDATE, ссылающаяся на первичный ключ, установленный на auto_increment, как упоминалось в других публикациях. Это предотвратит влияние обновлений на производительность обработки. Это также предотвратит неправомерное использование пользователями позже в очереди обработки значений от пользователей, которые были обработаны перед ними (если ранг одного пользователя влияет на ранг другого). Это также предотвращает очистку базы данных ее кэшей таблиц от SELECTS, которые выполняет ваш код обработки.

1 голос
/ 04 июня 2009

Возможно, вы можете использовать осколки по времени или другой категории. Но внимательно прочитайте это , прежде чем ...

1 голос
/ 04 июня 2009

Ваша проблема может быть решена несколькими способами. Честно говоря, больше информации с вашего сервера может указать вам совершенно другое направление. Но, делая это таким образом, вы вызываете 50000 маленьких блокировок на сильно читаемой таблице. Вы можете получить лучшую производительность с промежуточной таблицей, а затем с некоторым переходом. Вставки в таблицу, из которой никто не читает, вероятно, будут лучше.

Рассмотрим

mysql_query("delete from month_rank_staging;");
while(bla){
  mysql_query("insert into month_rank_staging values ('$id', '$i');");
}
mysql_query("update month_rank_staging src, users set users.month_rank=src.month_rank where src.id=users.id;");

Это вызовет одну (большую) блокировку на столе, но может улучшить вашу ситуацию. Но опять же, это может быть далеко от базы в зависимости от истинного источника вашей проблемы с производительностью. Вероятно, вам следует более подробно изучить ваши логи, конфигурацию mysql, соединения с базой данных и т. Д.

1 голос
/ 04 июня 2009

Каждый раз, когда у вас есть цикл любого значительного размера, который выполняет запросы внутри, вы получаете очень вероятный антипаттерн. Мы могли бы посмотреть на схему и требования к обработке с дополнительной информацией и посмотреть, сможем ли мы выполнить всю работу без цикла.

Сколько времени он тратит на подсчет баллов по сравнению с присвоением рейтинга?

1 голос
/ 04 июня 2009

Обновление таблицы пользователей строка за строкой будет трудоемкой задачей. Было бы лучше, если бы вы могли реорганизовать свой запрос так, чтобы построчное обновление не требовалось.

Я не уверен на 100% в синтаксисе (как я никогда раньше не использовал MySQL), но вот пример синтаксиса, используемого в MS SQL Server 2000

DECLARE @tmp TABLE
(
    [MonthRank] [INT] NOT NULL,
    [UserId] [INT] NOT NULL,
)

INSERT INTO @tmp ([UserId])
SELECT [id] 
FROM [users] 
WHERE [status] = '1' 
ORDER BY [month_score] DESC

UPDATE users 
SET month_rank = [tmp].[MonthRank]
FROM @tmp AS [tmp], [users]
WHERE [users].[Id] = [tmp].[UserId]

В MS SQL Server 2005/2008 вы, вероятно, используете CTE.

...