MySQL 使用大型数据集更新玩家每周排名位置的查询

MySQL query to update players weekly ranking positions with large dataset

我正在尝试更新玩家的每周排名分数,但我尝试过的任何查询都会超时。 table 中大约有 10 万行。我的 table players_weekly_rankings 看起来像这样:

player_id | ranking_points | yearweek | ranking_pos
22        | 1676           | 2020/01  | 1
12        | 1620           | 2020/01  | 2
45        | 1620           | 2020/01  | 2
53        | 1544           | 2020/01  | 4
25        | 1644           | 2020/02  | 1
21        | 1555           | 2020/02  | 2
etc.

因此 ranking_pos 列是正在更新的列。

以及永远不会完成并运行到超时的查询:

update players_weekly_ranking
set ranking_pos = (
    select count(distinct ranking_points) + 1
    from (SELECT ranking_points, yearweek FROM players_weekly_ranking) w2
    where w2.yearweek = players_weekly_ranking.yearweek and w2.ranking_points > players_weekly_ranking.ranking_points
)

和下面要求的 EXPLAIN(这个测试 tebale 只有 2000 条记录,但实际 table 接近 100k)

最多两千行,它会在两分钟内完成,但超过这个时间就会超时。

是否有更优化的方法来执行此操作,这样查询就不会 运行 超时?谢谢!

我相信这应该一次完成(没有子查询)

SET @rank = 0;

UPDATE players_weekly_ranking
SET ranking_pos = (@rank := @rank+1)
WHERE yearweek = '2020/01'
ORDER BY ranking_points DESC;

它定义了一个从 0 开始的变量 @rank,然后通过降序 ranking_points 遍历特定 yearweek 的所有行并分配它们 ranking_pos(@rank := @rank+1) 是否在每一行递增变量。

编辑:我假设你只需要更新特定一周的排名,因为过去的分数不应该改变

Edit2:这里是一个兼顾等分的版本,可以更新几个yearweeks:

SET @rank = 0; -- rank of the previous row
SET @yearweek = ''; -- yearweek of the previous row
SET @last_score = 0; -- score of the previous row
SET @nb_same_score = 0; -- number of rows "in a row" with same score

UPDATE players_weekly_ranking
SET ranking_pos = IF(
        @yearweek != (@yearweek := yearweek), 
        IF( -- if it's a new yearweek
            (@last_score := ranking_points) AND (@nb_same_score:=1),
            (@rank := 1), -- first row always gets ranking_pos = 1
            0
        ), 
        IF( -- if same yearweek
            @last_score = (@last_score := ranking_points) AND (@nb_same_score := @nb_same_score + 1),
            @rank, -- if same score as last row => set same ranking_pos
            @rank := @rank + @nb_same_score + (@nb_same_score := 1) -1
        )
    )
ORDER BY yearweek, ranking_points DESC;

迭代每一行,按年周和点排序,这会执行以下操作:

  • 如果是新的一周,第一行(最高分)的排名始终为 1。(@yearweek 取新一周的值,@last_score & @nb_same_score已重置)
  • 如果它与最后一行是同一周,@last_score 将与该行的得分进行比较(并更新)。如果它们相等,则 @nb_same_score 递增
    • 如果相等,该行与前一行的排名相同
    • 否则它的排名会增加 @nb_same_score。 ((@nb_same_score := 1) -1 只是为了将 @nb_same_score 变量重置为 1)
  1. 用 JOIN
  2. 替换 sub-query
  3. 在 JOIN 中 table 使用 window-function RANK() 计算排名。它会完全满足您的需求。

RANK() 仅适用于 MySQL 8+ https://dev.mysql.com/doc/refman/8.0/en/window-function-descriptions.html#function_rank

如果您有 MySQL 5.* 寻找没有 RANK() 函数的解决方法:Rank function in MySQL