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

У меня есть веб-сайт с рейтингом пользователя как центральная часть, но количество пользователей выросло до более чем 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]'"); } 

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

Вот код решения, который занимает менее 1/2 секунды для выполнения и обновления всех 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"); 

Сделайте userRanks.rank автоинкрементным первичным ключом. Если вы затем введете userid в 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 будет установлен в значение с добавочным значением для каждой строки.

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

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

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

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