设计高分/排行榜表

Ale*_*son 5 postgresql performance database-design

我正在寻找有关如何为我正在创建的游戏设计高分表的意见。

我使用的数据库是postgres。这不是我真的可以或不想改变的东西(除非有很好的理由)。

                                                          version
-----------------------------------------------------------------------------------------------------------------------------------
 PostgreSQL 9.3.4 on x86_64-apple-darwin13.1.0, compiled by Apple LLVM version 5.1 (clang-503.0.38) (based on LLVM 3.4svn), 64-bit
Run Code Online (Sandbox Code Playgroud)

我的目标是允许多达 10 000 000 行。我不确定这是否现实。

在我最初的尝试中,表格是最简单的表格(注意键aliasscore)。

 Column  |          Type          |                        Modifiers
---------+------------------------+---------------------------------------------------------
id       | integer                | not null default nextval('highscores_id_seq'::regclass)
traceid  | integer                |
alias    | character varying(256) |
score    | integer                |
rows     | integer                |
level    | integer                |
duration | integer                |
Indexes:
    "highscores_pkey" PRIMARY KEY, btree (id)
    "highscores_alias_idx" btree (alias)
    "highscores_score_idx" btree (score)
Run Code Online (Sandbox Code Playgroud)

我的问题最有趣的专栏是aliasscore(我把它们都包括在内,因为它们有一些我不知道的影响)。

查询

具体来说,我需要执行两个查询。一个,其选择高分,分类通过scoreOFFSETLIMIT(每个查询至多1000行)。我写过这个:

SELECT *, RANK() over(ORDER BY score DESC) AS rank FROM highscores OFFSET 0 LIMIT 100
Run Code Online (Sandbox Code Playgroud)

这适用于最高的结果(这是迄今为止最常见的),并且性能随着我制作的越大而降低OFFSET。不理想,但可以接受(我猜缓存在起作用)。

第二个查询更复杂;给定一个alias(例如一个用户的名字),它应该给出该用户获得的高分,以及每个高分在全局表中的位置(按 排序score)。

我是这样写的:

SELECT * FROM
  (SELECT *, RANK() OVER (ORDER BY score DESC) AS rank FROM highscores) AS tbl
WHERE alias = 'somealias'
Run Code Online (Sandbox Code Playgroud)

它给出了正确的结果(例如rank设置为表中的全局位置)。然而,它非常慢。最多需要30秒,实在是不能接受。

解释 (ANALYZE, BUFFERS) 这些查询给出以下(分别)

链接做 depesz

Limit  (cost=0.42..303.61 rows=100 width=35) (actual time=0.029..0.490 rows=100 loops=1)
  Buffers: shared hit=110
  ->  WindowAgg  (cost=0.42..606388.37 rows=200002 width=35) (actual time=0.028..0.469 rows=100 loops=1)
        Buffers: shared hit=110
        ->  Index Scan Backward using highscores_score_idx on highscores  (cost=0.42..603388.34 rows=200002 width=35) (actual time=0.015..0.264 rows=100 loops=1)
              Buffers: shared hit=110
Run Code Online (Sandbox Code Playgroud)

链接到 depesz

Subquery Scan on tbl  (cost=67440.85..73440.91 rows=1 width=43) (actual time=960.290..960.290 rows=0 loops=1)
    Filter: ((tbl.alias)::text = 'somealias'::text)
    Rows Removed by Filter: 200002
    Buffers: shared hit=3102 read=33788, temp read=1829 written=1829
    ->  WindowAgg  (cost=67440.85..70940.89 rows=200002 width=35) (actual time=560.130..920.911 rows=200002 loops=1)
          Buffers: shared hit=3102 read=33788, temp read=1829 written=1829
          ->  Sort  (cost=67440.85..67940.86 rows=200002 width=35) (actual time=560.117..652.521 rows=200002 loops=1)
                Sort Key: highscores.score
                Sort Method: external merge  Disk: 8208kB
                Buffers: shared hit=3102 read=33788, temp read=1829 written=1829
                ->  Seq Scan on highscores  (cost=0.00..38890.02 rows=200002 width=35) (actual time=111.099..215.351 rows=200002 loops=1)
                    Buffers: shared hit=3102 read=33788width=35)
Run Code Online (Sandbox Code Playgroud)

(来自一个包含 200 000 个随机生成的条目的表)。

帮助

我不确定我的桌子设计是否最佳。如果它对性能有任何帮助,我可以更改它。我猜查询也可以用不同的方式编写以提高性能。我看不出怎么办。任何指针将不胜感激!

Joe*_*own 2

假设您准备在答案的绝对正确性上做出妥协以获得实际性能,您可以执行以下操作:

  • 创建索引score(假设是降序,但任何一种方式都是可行的)
  • 创建维护作业以定期重建此索引(频率取决于您是否愿意让排名结果过时)

我认为您对这些假设没有意见,因为您已经score在问题中注明了索引。

那么您的用户的分数的全球排名将如下所示:

select 
  S.*
, count (R.id from highscores R
         where R.score > S.score) as rank
from highscores S
where S.alias = 'somealias'
Run Code Online (Sandbox Code Playgroud)

使用相关子查询并不是达到令人眼花缭乱的速度的秘诀,但这样做的好处是您不必对所有内容进行排名,您只需计算索引中相对于用户特定分数的条目即可。当然,只有您的查询优化器才能确定。