缓存大数据,替代查询或其他索引?

时间:2016-01-05 22:56:12

标签: php mysql caching

我遇到了问题,我正在制作高分榜,而对于那些高分,你需要根据技能经验和最新更新时间进行排名(看看谁获得最高分,首先获得技能经验是相同的)

问题是,对于我写的查询,创建个人高分页面需要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;

说明从查询中选择结果:

enter image description here

有人为我提供解决方案吗?

3 个答案:

答案 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 BYDESC,请为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;

虽然不太确定分组位。