Лучший способ обновить рейтинг пользователей, не убивая сервер

StackOverflow https://stackoverflow.com/questions/948832

Вопрос

У меня есть веб-сайт, на котором рейтинг пользователей занимает центральное место, но количество пользователей выросло до более чем 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 строк которого занимает менее 1/2 секунды (сделайте rank первичным ключом, как предложил Том Хейг).

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");
Это было полезно?

Решение

Сделать 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;

Другие советы

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

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

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

Это, по-видимому, было бы лучшим решением, поскольку затраты на повторное ранжирование были бы амортизированы в течение всех операций.

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

Простой альтернативой для массового обновления может быть что-то вроде:

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

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

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

Я не уверен на 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.

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

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

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

Рассмотреть

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, подключения к базе данных и т.д.

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

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top