我有一个简单的高分服务的在线游戏,它已经变得比预期更受欢迎.高分是一个Web服务,它使用带有简单表的MYSQL后端,如下所示.每个高分记录都存储在此表中的一行中.问题是,如果行数超过140k,我会发现某些关键查询速度过慢,以至于很快就无法为请求提供服务.
主表看起来像这样:
+----------+---------------+------+-----+---------+----------------+ | 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 BY rank DESC LIMIT 0, 75;"并以0.00秒运行.
但是,如果您在列表末尾请求
"SELECT * FROM scoretable WHERE game=1 AND time>? ORDER BY rank DESC LIMIT 10000, 75;"并在0.06秒内运行.
"SELECT * FROM scoretable WHERE game=1 AND time>? ORDER BY rank DESC LIMIT 100000, 75;" 并在0.58秒内运行.
这似乎会很快开始,因为每天提交几千个新分数!
此外,还有两种其他类型的查询,用于在排名顺序列表中按ID查找特定玩家.它们看起来像这样:
"SELECT * FROM scoretable WHERE game=1 AND time>? AND playerId=? ORDER BY rank DESC LIMIT 1"
接下来是
"SELECT count(id) as count FROM scoretable WHERE game=1 AND time>? AND rank>[rank returned from above]"
我的问题是:如何才能使这个可扩展的系统?我很快就能看到行数增长到数百万.我希望选择一些智能指数会有所帮助,但这种改善只是微不足道.
更新:这是一个解释线:
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 背景,但同样的想法也适用。
一些一般性观察:
100 万行确实不算多。我创建了一个像您这样的表,其中包含 1,000,000 行示例数据,即使使用一个索引(游戏 ASC、时间 DESC 和排名 DESC),所有查询的运行时间也不到 1 秒。
(我唯一不确定的部分是playerId。查询执行得很好,所以playerId似乎不是必需的。也许您可以将它添加到聚集索引的末尾。)