我遇到了问题,我正在制作高分榜,而对于那些高分,你需要根据技能经验和最新更新时间进行排名(看看谁获得最高分,首先获得技能经验是相同的)
问题是,对于我写的查询,创建个人高分页面需要28(技能)x 0,7秒才能看到他们的排名在列表中。在浏览器中请求这个是不可行的,加载页面需要太长时间,我需要一个解决方案来解决我的问题。
MySQL版本:5.5.47
我写的查询:
SELECT rank FROM
(
SELECT hs.playerID, (@rowID := @rowID + 1) AS rank
FROM
(
SELECT hs.playerID
FROM highscores AS hs
INNER JOIN overall AS o ON hs.playerID = o.playerID
WHERE hs.skillID = ?
AND o.game_mode = ?
ORDER BY hs.skillExperience DESC,
hs.updateTime ASC
) highscore,
(SELECT @rowID := 0) r
) data
WHERE data.playerID = ?
正如你所看到的,我首先必须创建一个完整的结果集,让我对该游戏模式和技能进行全面排名,然后我必须根据玩家ID选择排名,问题是我不能让查询运行直到它找到结果,因为mysql不提供这样的功能,如果我在上面的查询中指定where data.playerID = ?
,它将返回1个结果,这意味着排名也将为1。 / p>
highscores表有550k行
我所尝试的是将每个技能/游戏模式组合的结果集存储在临时表json_encoded
中,尝试存储在文件中,但最终也很慢,因为文件真的很大而且它需要时间来处理。
高分榜:
CREATE TABLE `highscores` (
`playerID` INT(11) NOT NULL,
`skillID` INT(10) NOT NULL,
`skillLevel` INT(10) NOT NULL,
`skillExperience` INT(10) NOT NULL,
`updateTime` BIGINT(20) NOT NULL,
PRIMARY KEY (`playerID`, `skillID`)
)
COLLATE='utf8_general_ci'
ENGINE=MyISAM;
整体表格有351k行
整体表格:
CREATE TABLE `overall` (
`playerID` INT(11) NOT NULL,
`playerName` VARCHAR(50) NOT NULL,
`totalLevel` INT(10) NOT NULL,
`totalExperience` BIGINT(20) NOT NULL,
`updateTime` BIGINT(20) NOT NULL,
`game_mode` ENUM('REGULAR','IRON_MAN','IRON_MAN_HARDCORE') NOT NULL DEFAULT 'REGULAR',
PRIMARY KEY (`playerID`, `playerName`)
)
COLLATE='utf8_general_ci'
ENGINE=MyISAM;
说明从查询中选择结果:
有人为我提供解决方案吗?
答案 0 :(得分:1)
WHERE没有有用的索引
EXPLAIN
(#3 DERIVED)的最后两行:
WHERE hs.skillID = ?
AND o.game_mode = ?
由于两个表都没有合适的索引用于WHERE
子句,因此优化器决定对其中一个(overall
)进行表扫描,然后到达另一个({{1 }})。拥有其中一个索引会有所帮助,至少有一些:
highscores
(更多信息。)
ORDER BY没有有用的索引
优化器有时决定为highscores: INDEX(skillID)
overall: INDEX(game_mode, ...) -- note that an index only on a low-cardinality ENUM is rarely useful.
使用ORDER BY
而不是的索引。但
WHERE
不能使用索引,即使两者都在同一个表中。这是因为DESC和ASC是不同的。将 ORDER BY hs.skillExperience DESC,
hs.updateTime ASC
更改为ASC
会对结果集产生影响,但会允许
DESC
待用。尽管如此,这可能不是最佳的。 (更多内容。)
覆盖索引
另一种优化形式是构建覆盖索引"。这是一个包含INDEX(skillExperience, updateTime)
所需的所有列的索引。然后,查询可以完全在索引中执行,而不会覆盖数据。有问题的SELECT
是最里面的:
SELECT
对于hs: ( SELECT hs.playerID
FROM highscores AS hs
INNER JOIN overall AS o ON hs.playerID = o.playerID
WHERE hs.skillID = ?
AND o.game_mode = ?
ORDER BY hs.skillExperience DESC, hs.updateTime ASC
) highscore,
是"覆盖"并且具有最重要的项目INDEX(skillID, skillExperience, updateTime, playerID)
,来自skillID
)第一个。
对于o:WHERE
是"覆盖"。同样,INDEX(game_mode, playerID)
必须是第一个。
如果您将game_mode
更改为ORDER BY
和DESC
,请为hs添加另一个索引:DESC
。现在前两列必须按顺序排列。
<强>结论强>
优化程序更喜欢哪些索引并不明显。我建议你加两个并让它选择。
我认为(1)最里面的查询占用了大量的时间,(2)在外部INDEX(skillExperience, updateTime, skillID, playerID)
中没有任何优化。所以,我把它作为我的建议。
我的Indexing Cookbook中涵盖了大部分内容。
答案 1 :(得分:0)
重要的subanswer:改变所有球员的排名的频率如何?嗯..需要解释..你想要实时统计吗?不,你不想要实时))你必须选择更新统计的时间间隔,例如10分钟。对于这种情况,您可以运行cronjob将新的排名统计信息插入到分离的表中,如下所示:
/* lock */
TRUNCATE TABLE rank_stat; /* maybe update as unused/old for history) instead truncate */
INSERT INTO rank_stat (a, b, c, d) <your query here>;
/* unlock */
和用户(浏览器)将从该表中选择只读统计信息(可以拆分为页面)。
但是如果排名统计不经常改变,例如你可以为所有想要的游戏事件和/或玩家的行为/成就重新计算它。
这只是推荐。因为你没有解释完整的环境。但我认为你可以通过这些建议找到正确的解决方案。
答案 2 :(得分:0)
看起来你真的需要对每个人进行排名,你只想知道有多少人领先于当前的玩家。你应该可以简单地计算出有多少玩家获得更好的分数&amp;比代表当前玩家排名的当前玩家的日期。
SELECT count(highscores.id) as rank FROM highscores
join highscores playerscore
on playerscore.skillID = highscores.skillID
and playerscore.gamemode = highscores.gamemode
where highscores.skillID = ?
AND highscores.gamemode = ?
and playerscore.playerID = ?
and (highscores.skillExperience > playerscore.skillExperience
or (highscores.skillExperience = playerscore.skillExperience
and highscores.updateTime > playerscore.updateTime));
(我加入了桌子并将第二个实例别名为playercore,所以它稍微有些混乱)
您甚至可以通过使用您选择的语言对结果进行分组和解析,将其简化为一个查询。
SELECT
highscores.gamemode as gamemode,
highscores.skillID as skillID,
count(highscores.id) as rank
FROM highscores
join highscores playerscore
on playerscore.skillID = highscores.skillID
and playerscore.gamemode = highscores.gamemode
where playerscore.playerID = ?
and (highscores.skillExperience > playerscore.skillExperience
or (highscores.skillExperience = playerscore.skillExperience
and highscores.updateTime > playerscore.updateTime));
group by highscores.gamemode, highscores.skillID;
虽然不太确定分组位。