如何使用Postgresql有效地获得一系列排名用户(对于排行榜)

Cha*_*pps 11 sql postgresql

我已经阅读了很多关于这个主题的帖子,比如 mysql-get-rank-from-leaderboards.

但是,没有一种解决方案能够从数据库中获得一系列排名.

问题很简单.假设我们有一个带有"id"列的Postgres表和另一个值不是唯一的INTEGER列,但我们有一个该列的索引.

例如,桌子可能是:

CREATE TABLE my_game_users (id serial PRIMARY KEY, rating INTEGER NOT NULL);
Run Code Online (Sandbox Code Playgroud)

目标

  • 为在"评级"列降序下订购用户的用户定义排名
  • 能够查询由这个新"排名"订购的约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;
Run Code Online (Sandbox Code Playgroud)

这"工作",但在快速笔记本电脑上,查询平均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;
Run Code Online (Sandbox Code Playgroud)

这是不错的,这个查询需要大约120毫秒,100,000个用户具有随机评级.但是,这仅返回具有特定id(2000)的用户的等级.

我看不到任何有效的方法来扩展此查询以获得一系列排名.任何扩展此尝试的尝试都会导致查询速度变慢.

我只知道"中心"用户的ID,因为在我们知道哪些用户在该范围内之前,用户必须按等级排序!

尝试#3:内存中有序树

我最终使用Java TreeSet来存储排名.每当新用户插入数据库或用户的评级发生变化时,我都可以更新TreeSet.

这是超快的,大约25毫秒,有100,000个用户.

但是,它有一个严重的缺点,它只在为请求提供服务的Webapp节点上更新.我正在使用Heroku并将为我的应用部署多个节点.所以,我需要为服务器添加一个计划任务,每小时重建一次这个排名树,以确保节点不会太不同步!

如果有人知道在Postgres中使用完整解决方案做到这一点的有效方法,那么我全都耳朵!

小智 0

如果您按降序排序,则您的顺序是正确的。使用 rownumber() 函数。 在 postgres 中选择行号

您还可以使用内存缓存将内容存储在内存中。像redis之类的东西。它是一个独立的应用程序,可以为多个实例提供服务,甚至可以远程服务。