【发布时间】:2012-07-19 02:46:42
【问题描述】:
我正在研究如何为我的公司发布的 Facebook 赛车游戏实施全球排行榜。我想做的是能够存储玩家的用户 ID 和他们的比赛时间。我有一张如下表:
+--------+-----------------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+--------+-----------------------+------+-----+---------+-------+
| userID | mediumint(8) unsigned | NO | PRI | 0 | |
| time | time | YES | MUL | NULL | |
+--------+-----------------------+------+-----+---------+-------+
还有这样一组样本数据:
+--------+----------+
| userID | time |
+--------+----------+
| 505610 | 10:10:10 |
| 544222 | 10:10:10 |
| 547278 | 10:10:10 |
| 659241 | 10:10:10 |
| 681087 | 10:10:10 |
+--------+----------+
我的查询将来自 PHP。现在,如果我假设我有无限的资源,我能做的就是:
$q1 = "Set @rank := 0";
$q2 = "select @rank:=@rank+1 as rank,userID,time from highscore order by time asc where userID=$someUserID";
$q3 = "Set @rank := 0";
$q4 = "select @rank:=@rank+1 as rank,userID,time from highscore order by time asc where rank > $rankFromSecondQuery - 10 and rank < $rankFromSecondQuery + 10";
但我没有无限的资源,我必须能够扩展它以支持数百万玩家,因为它正在进入 Facebook 上的社交游戏。因此,在花了几天时间在 Google 上进行爬网之后,我已经能够将我的查询归结为:
$q5 = "select rank,userID,time from (select @rank:=0) r, (select @rank:=@rank+1 as rank,userID,time from highscore order by time asc) as myMine where userID=$someUserID"
$q6 = "select rank,userID,time from (select @rank:=0) r, (select @rank:=@rank+1 as rank,userID,time from highscore order by time asc) as myMine where rank > $rankFromFirstQuery - 10 and rank < $rankFromSecondQuery + 10";
这可行,但它不是很漂亮,每个查询的平均运行时间约为 2.3 秒。
编辑:这是 $q5 和 $q6 在运行它们时给我的:
mysql> select rank,userID,time from (select @rank:=0) r, (select @rank:=@rank+1 as rank,userID,time from highscore order by time asc) as myMine where userID=11345;
+--------+--------+----------+
| rank | userID | time |
+--------+--------+----------+
| 423105 | 11345 | 12:47:23 |
+--------+--------+----------+
1 row in set (2.42 sec)
mysql> select rank,userID,time from (select @rank:=0) r, (select @rank:=@rank+1 as rank,userID,time from highscore order by time asc) as myMine where rank>423100 and rank<423110;
+--------+---------+----------+
| rank | userID | time |
+--------+---------+----------+
| 423101 | 2416665 | 12:47:22 |
| 423102 | 2419720 | 12:47:22 |
| 423103 | 2426606 | 12:47:22 |
| 423104 | 2488517 | 12:47:22 |
| 423105 | 11345 | 12:47:23 |
| 423106 | 92350 | 12:47:23 |
| 423107 | 94277 | 12:47:23 |
| 423108 | 114685 | 12:47:23 |
| 423109 | 135434 | 12:47:23 |
+--------+---------+----------+
9 rows in set (2.58 sec)
这是解释扩展块 $q5 和 $q6 的解释扩展块看起来几乎相同:
mysql> explain select rank,userID,time from (select @rank:=0) r, (select @rank:=@rank+1 as rank,userID,time from highscore order by time asc) as myMine where userID=11345;
+----+-------------+------------+--------+---------------+----------+---------+------+---------+----------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+------------+--------+---------------+----------+---------+------+---------+----------------+
| 1 | PRIMARY | <derived2> | system | NULL | NULL | NULL | NULL | 1 | |
| 1 | PRIMARY | <derived3> | ALL | NULL | NULL | NULL | NULL | 2500000 | Using where |
| 3 | DERIVED | highscore | index | NULL | idx_time | 4 | NULL | 2500842 | Using index |
| 2 | DERIVED | NULL | NULL | NULL | NULL | NULL | NULL | NULL | No tables used |
+----+-------------+------------+--------+---------------+----------+---------+------+---------+----------------+
所以最终,我真正想做的就是将其简化为一个查询,这样我就可以使用一两台高 CPU 的服务器来缓和执行时间。要么就是这样,要么我想找出一种方法来在查询部分中命中索引,该查询与在表中的所有行命中的解释块中的派生3 行相关联。
以下是我迄今为止尝试过的几个查询,但均未成功:
select rank,userID,time from (select @rank:=0) r, (select @playerRank := rank from (select @rank:=@rank+1 as rank,userID,time from highscore order by time asc) as myMine where userID=11345) as myFoo where @playerRank>423100 and @playerRank<423110;
select rank,userID,time from (select @playerRank := rank from (select @rank := 0) r, (select @rank:=@rank+1 as rank,userID,time from highscore order by time asc) as myMine where userID=11345) as myFoo where @playerRank>423100 and @playerRank<423110;
select * from (select @rank:=0) r, (select @playerRank := userID from (select @rank:=@rank+1 as rank,userID,time from highscore order by time asc) as myMine where userID=11345) as myFoo where @playerRank>423100 and @playerRank<423110;
前两个游戏给我一个“错误 1054 (42S22): Unknown colum 'rank' in 'field list' 错误,第三个只是返回一个空集而不是我正在寻找的数据。
任何人都知道如何让上面列出的两个查询命中索引以减少执行时间,或者如何将两个查询合并为一个,这样我只需要忍受一次痛苦的执行时间吗?如果有人有使用类似东西的经验并想分享他们的经验,我也愿意接受调整/优化,例如调整 MySQL 配置设置和/或使用 Percona 之类的东西。
【问题讨论】:
-
查询的示例结果集是什么?
-
我编辑了我的原始帖子以包含两个查询的结果集。
-
不是每次有人想查看排名时都计算这个,您是否考虑过将排名存储在表格中并在每次有人记录新分数时更新它?这将导致很少的计算。
-
我想过那样做。将每个用户的当前排名存储在表中。不利的一面是,我必须不断地用新计算的等级更新表格。虽然如果您查看 20 行并不一定很糟糕,但如果表中有 250 万行,并且有人刚刚更新到排名 2,那么我必须更新超过 240 万条记录。
-
只有在用户第一次记录分数时才会出现这种情况。实际上,大多数更新可能只是分数的局部小变化,因为玩家不会经常大幅超过他们之前的高分。
标签: php mysql database optimization