在不杀死服务器的情况下更新用户排名的最佳方法

时间:2009-06-04 06:17:19

标签: php mysql optimization query-optimization ranking

我有一个以用户排名为中心的网站,但是用户数已增加到超过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秒(按照Tom Haigh的建议将等级设为主键)。

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 个答案:

答案 0 :(得分:8)

使userRanks.rank成为自动增量主键。如果然后以降序排序将用户ID插入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;

答案 1 :(得分:3)

我的第一个问题是:你为什么每五分钟进行一次轮询式操作?

当然,排名更改将响应某些事件,您可以在发生该事件时将更改本地化到数据库中的几行。我非常确定50,000的整个用户群不会每五分钟更改一次排名。

我假设"status = '1'"表示用户的排名发生了变化,而不是在用户触发排名更改时设置此项,为什么不计算当时的排名呢?

这似乎是一个更好的解决方案,因为重新排名的成本将在所有业务中摊销。

现在我可能误解了你的排名意味着在哪种情况下随时让我直截了当。

答案 2 :(得分:3)

批量更新的简单替代方法可能是:

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

此代码使用一个局部变量(@rnk),该变量在每次更新时递增。由于更新是在有序的行列表上完成的,因此month_rank列将设置为每行的递增值。

答案 3 :(得分:1)

逐行更新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。

答案 4 :(得分:1)

任何时候你有一个任何重要大小的循环在里面执行查询,你有一个非常可能的反模式。我们可以通过更多信息查看模式和处理需求,看看我们是否可以在没有循环的情况下完成整个工作。

与分配排名相比,计算得分的时间是多少?

答案 5 :(得分:1)

您的问题可以通过多种方式解决。老实说,服务器上的更多细节可能会指向一个完全不同的方向。但是这样做会导致重读表上有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配置,数据库连接等。

答案 6 :(得分:1)

您可以按时间或其他类别使用shards。但在......之前仔细阅读this

答案 7 :(得分:1)

您可以拆分排名处理和更新执行。因此,遍历所有数据并处理查询。将每个更新语句添加到缓存。处理完成后,运行更新。您应该将UPDATE引用的WHERE部分设置为auto_increment的主键,如其他帖子中所述。这将防止更新干扰处理的性能。它还会阻止稍后处理队列中的用户错误地利用在他们之前处理的用户的值(如果一个用户的排名影响另一个用户的排名)。它还可以防止数据库从您处理代码的SELECTS中清除其表缓存。