【发布时间】:2015-10-24 09:22:41
【问题描述】:
我已经阅读了很多关于这个主题的帖子,例如 mysql-get-rank-from-leaderboards.
但是,没有一种解决方案能够大规模有效地从数据库中获取一系列排名。
问题很简单。假设我们有一个 Postgres 表,其中有一个“id”列和另一个值不唯一的 INTEGER 列,但我们有该列的索引。
例如表可能是:
CREATE TABLE my_game_users (id serial PRIMARY KEY, rating INTEGER NOT NULL);
目标
- 为在“评级”列中按降序排列用户的用户定义排名
- 能够查询按此新“排名”排序的约 50 个用户的列表,以任何特定用户为中心
- 例如,我们可能返回排名为 { 15, 16, ..., 64, 65 } 的用户,其中中心用户的排名为 #40
- 性能必须可扩展,例如对于 100,000 个用户,时间低于 80 毫秒。
尝试 #1:row_number() 窗口函数
WITH my_ranks AS
(SELECT my_game_users.*, row_number() OVER (ORDER BY rating DESC) AS rank
FROM my_game_users)
SELECT *
FROM my_ranks
WHERE rank >= 4000 AND rank <= 4050
ORDER BY rank ASC;
这“有效”,但查询平均为 550 毫秒,100,000 名用户在一台快速笔记本电脑上,没有完成任何其他实际工作。
我尝试添加索引,并重新表述此查询以不使用“WITH”语法,但没有任何方法可以加快速度。
尝试 #2 - 计算评分值较高的行数 我试过这样的查询:
SELECT t1.*,
(SELECT COUNT(*)
FROM my_game_users t2
WHERE (t1.rating, -t1.id) <= (t2.rating, -t2.id)
) AS rank
FROM my_game_users t1
WHERE id = 2000;
这很不错,这个查询大约需要 120 毫秒,有 100,000 个用户有随机评分。但是,这只会返回具有特定 id (2000) 的用户的排名。
我看不到任何有效的方法来扩展此查询以获得一系列排名。任何扩展它的尝试都会使查询变得非常缓慢。
我只知道“中心”用户的 ID,因为在我们知道哪些用户在范围内之前,用户必须按等级排序!
尝试 #3:内存中的有序树
我最终使用 Java TreeSet 来存储排名。每当将新用户插入数据库或用户评分发生变化时,我都可以更新 TreeSet。
这非常快,大约 25 毫秒,有 100,000 个用户。
但是,它有一个严重的缺点,即它只在为请求提供服务的 Webapp 节点上更新。我正在使用 Heroku,将为我的应用部署多个节点。所以,我需要为服务器添加一个计划任务,以每小时重新构建这个排名树,以确保节点不会太不同步!
如果有人知道在 Postgres 中使用完整解决方案执行此操作的有效方法,那么我会全力以赴!
【问题讨论】:
-
根据您的更新工作方式,或许可以考虑物化视图?
-
在你的第一次尝试中,为什么你在 over 函数中排序
desc然后你又重新排序为 asc?我认为如果你只使用一个订单(在你的情况下是 over 函数),它可能会更快。 -
就 Node JS 而言,对于性能关键的请求,请考虑使用 github.com/brianc/node-pg-query-stream
-
你在使用redis吗?如果你是 redis zorted 套装是为此目的量身定做的,而且速度非常快且可扩展。
标签: sql postgresql