Mysql查询语句索引调优

时间:2012-07-19 20:40:55

标签: php mysql database optimization

我正在研究如何为我公司发布的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的社交游戏。因此,在花了几天时间在谷歌上爬行之后,我已经能够对此进行查询:

$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服务器来缓和执行时间。无论是那个,还是我想找出一种方法,只需点击查询部分的索引,该索引与解释块中的derive3行相关联,该行触及表中的所有行。

到目前为止,这是我尝试过的几个没有任何成功的查询:

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;

前两个游戏我在“字段列表”错误中出现“ERROR 1054(42S22):未知列'排名',第三个只返回空集而不是我要查找的数据。

任何人都有任何想法如何获得上面列出的两个查询来查找索引以便执行时间减少或如何将两个查询组合成一个所以我只需要忍受一次痛苦的执行时间?我也愿意调整/优化,例如调整MySQL配置设置和/或使用像Percona这样的东西,如果有人有经验使用类似的东西,并希望分享他们的经验。

3 个答案:

答案 0 :(得分:0)

运行$q5之后你应该知道用户的等级,之后你应该能够使用限制来获得正确的行

$lowest_rank_to_fetch = max(0, $rankFromFirstQuery - 10);
$q6l = "SELECT userID, time
        FROM highscore
        ORDER BY time ASC
        LIMIT {$lowest_rank_to_fetch}, 21";

/* some execute query function */

foreach(range($lowest_rank_to_fetch, $lowest_rank_to_fetch+21) as $current_rank)
{
   /* some database fetch function */
   /* add $current_rank to result */
}

答案 1 :(得分:0)

您可以首先使用count()获得排名,这应该对第一个查询执行得更好:

SELECT COUNT(h.userID) as rank, h2.userID, h2.time
   FROM highscore h
   LEFT OUTER JOIN highscore h2 ON (h.time <= h2.time)
   WHERE h2.userID = ?

然后你可以使用Puggan的技术来查询附近的排名。

SELECT ... ORDER BY time LIMIT $lowest_rank, 21

答案 2 :(得分:0)

我想提出这个替代解决方案来实现你想要实现的目标。

制作一个单独的表来存储排名。每次用户想要了解他/她的等级时都不要计算它,也不要将其包含在现有表中。当排名更新与排名计算竞争时,将排名放在单独的表中将有望缓解锁争用问题。

定期重新计算排名。执行此重新计算时,请通过截断排名表并从头重新创建它来执行此操作。使用批量加载操作(LOAD DATA INFILE)或将其设置为MyISAM表(在表的末尾插入时速度很快)。无论哪种方式都应该相对快速地实际写出表格;至少比更新已经存在的表中的数百万行更快。这两种方法都会使您的排名表变脆并且在发生崩溃时容易丢失,但这没关系,因为这本质上是瞬态数据。只要你的分数表稳定,你就安全了。通过定期重新计算,您可以避免在播放次数增加之前越来越频繁地进行计算的问题。

如果用户在前100名中得分,请立即推出他们的新分数。用户可能想要浏览前100名以查看谁得分最高。我认为没有人想要在这一点下面实际浏览列表。

允许用户立即查看他们朋友的分数,以及他们相对于彼此的相对等级。这可能是大多数用户感兴趣的排名。我知道当我的妻子玩Facebook游戏时,她对她的整体排名没有兴趣,但她非常想知道她是否打败了她的大学同学。

显示玩家的整体排名及其朋友的排名,在用户最新播放后失效,并在下次更新准备就绪时异步加载。

另一个考虑因素是,如果这个游戏将会存在几年,那么你的记分牌最终会被非活跃玩家的旧分数堵塞,尤其是低端玩家。您可能想要考虑是否值得归档这些分数。例如,您可以说,如果他们在过去6个月内进行了比赛,则只有75%的记分牌中的任何一名球员才会被考虑在排名中。然后,将他们的分数移到存档表中,在那里他们将被记住,并且如果该玩家返回,则可以恢复到记分板,但是每次计算排名时都不必包括在排序中。是的,这可能会让你的排名变得不那么“真实”,但无论如何人们只是为了好玩。它会产生副作用,使他们的排名看起来更好,这也很有趣。记分牌上的一些细则会简要提及旧的分数不包括在内,所以你仍然可以说一切都在董事会之上。