我有一个简单的在线游戏高分服务,它比预期更受欢迎。得分最高的是一个 Web 服务,它使用 MYSQL 后端,并带有一个简单的表,如下所示。每个高分记录都作为一行存储在此表中。问题是,对于超过 140k 行,我发现某些关键查询速度减慢太多,以至于服务请求很快就会变得太慢。
主表如下所示:
- id 是每个分数记录的唯一键
- game是提交分数的游戏的ID号(目前始终等于“1”,不过很快就会支持更多游戏)
- name 是该玩家提交的内容的显示名称
- playerId 是给定用户的唯一 ID
- score 是一个数字分数表示形式,例如 42,035
- time为提交时间
- 排名是一个大整数,它对给定游戏的分数提交进行唯一排序。这是 人们通常会在一定分数上打平,因此在这种情况下,平局将由谁先提交而打破。因此该字段的值大致等于“score * 100000000 + (MAX_TIME - time)”
+----------+---------------+------+-----+---------+----------------+ | Field | Type | Null | Key | Default | Extra | +----------+---------------+------+-----+---------+----------------+ | id | int(11) | NO | PRI | NULL | auto_increment | | game | int(11) | YES | MUL | NULL | | | name | varchar(100) | YES | | NULL | | | playerId | varchar(50) | YES | | NULL | | | score | int(11) | YES | | NULL | | | time | datetime | YES | | NULL | | | rank | decimal(50,0) | YES | MUL | NULL | | +----------+---------------+------+-----+---------+----------------+
索引如下所示:
+-----------+------------+----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+ | Table | Non_unique | Key_name | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment | +-----------+------------+----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+ | pozscores | 0 | PRIMARY | 1 | id | A | 138296 | NULL | NULL | | BTREE | | | pozscores | 0 | game | 1 | game | A | NULL | NULL | NULL | YES | BTREE | | | pozscores | 0 | game | 2 | rank | A | NULL | NULL | NULL | YES | BTREE | | | pozscores | 1 | rank | 1 | rank | A | 138296 | NULL | NULL | YES | BTREE | | +-----------+------------+----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+
当用户请求高分时,他们通常会请求“按排名降序排序列表”中任意点的大约 75 个高分。这些请求通常针对“所有时间”或仅针对过去 7 天内的分数。
典型的查询如下所示:
“SELECT * FROM Scoretable WHERE game=1 AND time>? ORDER BYrank DESC LIMIT 0, 75;”
并在 0.00 秒内运行。
但是,如果您请求接近列表末尾
“SELECT * FROM Scoretable WHERE game=1 AND time>? ORDER BYrank DESC LIMIT 10000, 75;”
运行时间为 0.06 秒。
“SELECT * FROM Scoretable WHERE game=1 AND time>? ORDER BYrank DESC LIMIT 100000, 75;”
并在 0.58 秒内运行。
由于每天都会提交数千个新分数,这似乎很快就会开始花费很长时间!
此外,还有两种其他类型的查询,用于根据排名顺序列表中的 id 查找特定玩家。 它们看起来像这样:
“SELECT * FROM Scoretable WHERE game=1 AND time>? AND playerId=? ORDER BYrank DESC LIMIT 1”
后跟
“从得分表中选择计数(id)作为计数,其中游戏= 1和时间>?AND排名>[从上面返回的排名]”
我的问题是:如何使其成为一个可扩展的系统?我可以看到行数很快就会增长到几百万。我希望选择一些智能索引会有所帮助,但改善幅度很小。
更新: 这是一条解释行:
mysql> explain SELECT * FROM scoretable WHERE game=1 AND time>0 ORDER BY rank DESC LIMIT 100000, 75; +----+-------------+-----------+-------+---------------+------+---------+------+--------+-------------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+-----------+-------+---------------+------+---------+------+--------+-------------+ | 1 | SIMPLE | scoretable| range | game | game | 5 | NULL | 138478 | Using where | +----+-------------+-----------+-------+---------------+------+---------+------+--------+-------------+<小时/>
找到解决方案!
由于该线程中的一些指示,我已经解决了该问题。做聚集索引正是我所需要的,所以我将表转换为使用mysql中的InnoDB,它支持聚集索引。接下来,我删除了 id 字段,并将主键设置为(游戏 ASC,排名 DESC)。现在,无论我使用什么偏移量,所有查询都运行得非常快。解释显示没有进行任何额外的排序,并且看起来它可以轻松处理所有流量。
最佳答案
既然没有人接受,那我就试试吧。我有 SQL Server 背景,但同样的想法也适用。
一些一般性观察:
- ID 列几乎毫无意义,不应该参与任何索引,除非还有您没有告诉我们的其他表/查询。事实上,它甚至不需要出现在您的最后一个查询中。您可以执行 COUNT(*) 操作。
- 您的聚集索引应针对最常见的查询。因此,游戏 ASC、时间 DESC 和排名 DESC 的聚集索引效果很好。对于这样的历史表,按时间 DESC 排序通常是一个好主意,因为您通常对最新的内容感兴趣。您也可以尝试使用单独的索引,并将排名按另一个方向排序,尽管我不确定这会有多大好处。
- 您确定需要 SELECT * 吗?如果您可以选择更少的列,您也许能够创建一个包含 SELECT 和 WHERE 所需的所有列的索引。
100 万行确实不算多。我创建了一个像您这样的表,其中包含 1,000,000 行示例数据,即使使用一个索引(游戏 ASC、时间 DESC 和排名 DESC),所有查询的运行时间也不会超过 1 秒。
(我唯一不确定的部分是playerId。查询执行得很好,所以playerId似乎不是必需的。也许您可以将它添加到聚集索引的末尾。)
关于mysql - 扩展高分数据库,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/4858693/