3

我有一个将用户排名作为中心部分的网站,但用户数量已增长到超过 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");
4

8 回答 8

8

创建userRanks.rank一个自动递增的主键。userRanks如果您随后按降序插入用户 ID ,它将增加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;
于 2009-06-04T10:58:46.463 回答
3

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

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

我假设"status = '1'"表示用户的排名已经改变,而不是在用户触发排名改变时设置这个,你为什么不计算当时的排名?

这似乎是一个更好的解决方案,因为重新排名的成本将分摊到所有操作中。

现在我可能误解了你所说的排名是什么意思,在这种情况下,请随时让我直截了当。

于 2009-06-04T06:25:27.790 回答
3

批量更新的简单替代方法可能类似于:

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

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

于 2009-06-04T06:34:55.387 回答
1

逐行更新用户表将是一项耗时的任务。如果您可以重新组织查询以便不需要逐行更新,那就更好了。

我不是 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。

于 2009-06-04T06:23:55.967 回答
1

任何时候,只要您有一个在内部执行查询的任何显着大小的循环,您就有一个很可能是反模式。我们可以通过更多信息查看架构和处理要求,看看我们是否可以在没有循环的情况下完成整个工作。

与分配排名相比,它花费了多少时间来计算分数?

于 2009-06-04T06:28:20.327 回答
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 配置、数据库连接等。

于 2009-06-04T06:37:09.720 回答
1

可能您可以按时间或其他类别使用分片。但在之前请仔细阅读...

于 2009-06-04T10:57:57.913 回答
1

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

于 2009-06-05T03:24:18.327 回答