0

我的游戏服务器上有一个巨大的瓶颈,用于存储当前排行榜的以下查询。

我目前仅每 5 分钟通过 cron 调用一次此查询,但希望将其优化到足以每分钟或在需要时调用。

查询耗时 30 秒,目前只有约 2000 名用户和 7000 场比赛(存储在 Games 和 TopPlayerScores 中)。恐怕只会越来越严重!!请帮帮我溢出-克诺比!你是我唯一的希望!

SET @rank=0;
INSERT INTO Board (TopScorePKID, GamePKID, UserPKID, UniquePlayerID, PlayerName, TopPlayerScore, Position, Date)
(SELECT bad.ID AS TopScorePKID, bad.GamePKID, bad.UserPKID, bad.UniquePlayerID, bad.PlayerName, bad.TopPlayerScore, @rank:=@rank+1 AS Position, bad.Date
FROM (
    SELECT g.GamePKID, g.TopPlayerScore, l.ID,  l.UserPKID, u.UniquePlayerID, u.PlayerName, (l.Date) AS Date
    FROM Games g, TopPlayerScores l, UserDetails u
    WHERE l.GamePKID = g.GamePKID
    AND u.UserPKID = l.UserPKID
    AND u.SECRET_DETAIL = 0 
    AND g.TopPlayerScore >= (SELECT DISTINCT k.TopPlayerScore AS Highest 
        FROM Games k, TopPlayerScores t 
        WHERE t.UserPKID = l.UserPKID
        AND k.GamePKID = t.GamePKID
        ORDER BY k.TopPlayerScore DESC
        LIMIT 1) 
    GROUP BY l.UserPKID
    ORDER BY g.TopPlayerScore DESC, Date ASC) 
AS bad);

请有人帮忙!!我应该把它分解成视图吗?还是使用内连接关键字?最好的方法是什么?

非常感谢您甚至看到这个烂摊子:D!

更新 1.0: 解释扩展结果:

id select_type 表类型 possible_keys key key_len ref rows 已过滤 Extra
1 PRIMARY ALL NULL NULL NULL NULL 1521 100.00  
2 DERIVED l ALL NULL NULL NULL NULL 6923 100.00 使用临时;使用文件排序
2 DERIVED u eq_ref PRIMARY PRIMARY 4 DBNAME.l.UserPKID 1 100.00 使用 where
2 DERIVED k eq_ref PRIMARY PRIMARY 4 DBNAME.l.GamePKID 1 100.00 使用 where
3 相关子查询 t ALL NULL NULL NULL NULL 6923 100.00 使用 where;使用临时的;使用文件排序
3 DEPENDENT SUBQUERY g eq_ref PRIMARY PRIMARY 4 DBNAME.t.GamePKID 1 100.00 使用 where

更新 2.0: 查询表的有限模式

使用游戏存储游戏分数和其他有关游戏的信息

`Games` (
  `GamePKID` int(11) NOT NULL AUTO_INCREMENT,
  `TopPlayerScore` int(11) NOT NULL,
  `OTHER_MISC_STUFF_REMOVED` int(11) NOT NULL
  PRIMARY KEY (`GamePKID`)
)

使用以下内容将用户链接到游戏并存储时间/日期

`TopPlayerScores` (
  `ID` int(11) NOT NULL AUTO_INCREMENT,
  `UserPKID` int(11) NOT NULL,
  `GamePKID` int(11) NOT NULL,
  `Date` datetime NOT NULL,
  PRIMARY KEY (`ID`)
)

用于存储每个唯一的玩家

`UserDetails` (
  `UserPKID` int(11) NOT NULL AUTO_INCREMENT,
  `UniquePlayerID` char(40) NOT NULL,
  `PlayerName` char(96) NOT NULL,
  `SECRET_DETAIL` tinyint(1) NOT NULL DEFAULT '0',
  `isPlayer` tinyint(4) DEFAULT NULL,
  PRIMARY KEY (`UserPKID`)
)
4

1 回答 1

4

我要注意的第一件事,虽然这不会提高性能,但您使用的 JOIN 语法在 20 多年前已被 ANSI 92 expcict join 语法取代,当然它完全是主题,但Aaron Bertrand 解释了一些非常好的切换到较新语法的原因。

要注意的第二件事是您的结果将是不确定的。您正在选择未包含在聚合或分组依据中的列。虽然 MySQL 允许这样做,但您并没有按照 MySQL 的预期使用该功能。MySQL 文档状态:

MySQL 扩展了 GROUP BY 的使用,以便选择列表可以引用未在 GROUP BY 子句中命名的非聚合列。这意味着前面的查询在 MySQL 中是合法的。您可以使用此功能通过避免不必要的列排序和分组来获得更好的性能。但是,这主要在每个未在 GROUP BY 中命名的非聚合列中的所有值对于每个组都相同时很有用。服务器可以从每个组中自由选择任何值,因此除非它们相同,否则选择的值是不确定的。

但是,您包含的某些列 ( g.GamePKID, g.TopPlayerScore, l.ID, l.Date) 不满足条件对于每个组都是相同的,因此,如前所述,MySQL 可以自由选择它喜欢的任何值,即使您有ORDER BY g.TopPlayerScore DESC, Date ASC这个也不影响 MySQL 选择的每个组的单行。

第三,MySQL 对相关子查询有限制,这会影响性能。如果您可以将这些更改为 JOIN,您应该会看到性能提升。

考虑到所有这些,我将这样重写您的查询:

SET @rank=0;
INSERT INTO Board (TopScorePKID, GamePKID, UserPKID, UniquePlayerID, PlayerName, TopPlayerScore, Position, Date)
SELECT  bad.ID AS TopScorePKID, 
        bad.GamePKID, 
        bad.UserPKID, 
        bad.UniquePlayerID, 
        bad.PlayerName, 
        bad.TopPlayerScore, 
        @rank:=@rank+1 AS Position, 
        bad.Date
FROM    (   SELECT  g.GamePKID, 
                    g.TopPlayerScore, 
                    l.ID,  
                    l.UserPKID, 
                    u.UniquePlayerID, 
                    u.PlayerName, 
                    l.Date
            FROM    Games g
                    INNER JOIN TopPlayerScores l
                        ON l.GamePKID = g.GamePKID
                    INNER JOIN UserDetails u
                        ON u.UserPKID = l.UserPKID
                    INNER JOIN
                    (   SELECT  TopPlayerScores.UserPKID, MAX(games.TopPlayerScore) AS MaxPlayerScore
                        FROM    TopPlayerScores
                                INNER JOIN Games
                                    ON Games.GamePKID = TopPlayerScores.GamePKID
                        GROUP BY TopPlayerScores.UserPKID
                    ) MaxScore
                        ON MaxScore.UserPKID = l.UserPKID
                        AND MaxScore.MaxPlayerScore = g.TopPlayerScore
            WHERE   u.SECRET_DETAIL = 0 
        ) AS bad
ORDER BY bad.TopPlayerScore DESC, bad.Date ASC;

SQL Fiddle 示例

子查询MaxScore应该具有将结果限制为每个玩家一行的效果(只有他们的最高分),尽管可能需要额外的逻辑来处理平局(例如,玩家在多场比赛中拥有相同的最高分)。在不知道确切要求的情况下,我无法纠正这一点。

编辑

为了删除玩家在 2 个或更多游戏中具有相同最高分的重复项,并使其真正具有确定性,您需要添加一个进一步的子查询:

SET @rank=0;

SELECT  bad.ID AS TopScorePKID, 
        bad.GamePKID, 
        bad.UserPKID, 
        bad.UniquePlayerID, 
        bad.PlayerName, 
        bad.TopPlayerScore, 
        @rank:=@rank+1 AS Position, 
        bad.Date
FROM    (   SELECT  Games.GamePKID, 
                    Games.TopPlayerScore, 
                    TopPlayerScores.ID,  
                    TopPlayerScores.UserPKID, 
                    UserDetails.UniquePlayerID, 
                    UserDetails.PlayerName, 
                    TopPlayerScores.Date
            FROM    Games
                    INNER JOIN TopPlayerScores
                        ON TopPlayerScores.GamePKID = Games.GamePKID
                    INNER JOIN UserDetails
                        ON UserDetails.UserPKID = TopPlayerScores.UserPKID
                    INNER JOIN
                    (   SELECT  TopPlayerScores.UserPKID, MAX(games.TopPlayerScore) AS TopPlayerScore
                        FROM    TopPlayerScores
                                INNER JOIN Games
                                    ON Games.GamePKID = TopPlayerScores.GamePKID
                        GROUP BY TopPlayerScores.UserPKID
                    ) MaxScore
                        ON MaxScore.UserPKID = TopPlayerScores.UserPKID
                        AND MaxScore.TopPlayerScore = Games.TopPlayerScore
                    INNER JOIN
                    (   SELECT  TopPlayerScores.UserPKID, games.TopPlayerScore, MAX(Date) AS Date
                        FROM    TopPlayerScores
                                INNER JOIN Games
                                    ON Games.GamePKID = TopPlayerScores.GamePKID
                        GROUP BY TopPlayerScores.UserPKID, games.TopPlayerScore
                    ) MaxScoreDate
                        ON MaxScoreDate.UserPKID = TopPlayerScores.UserPKID
                        AND MaxScoreDate.TopPlayerScore = Games.TopPlayerScore
                        AND MaxScoreDate.Date = TopPlayerScores.Date
            WHERE   UserDetails.SECRET_DETAIL = 0 
        ) AS bad
ORDER BY bad.TopPlayerScore DESC, bad.Date ASC;

SQL Fiddle 示例


ROW_NUMBER()注意:如果/当 MySQL 引入分析函数(例如

SELECT  bad.ID AS TopScorePKID, 
        bad.GamePKID, 
        bad.UserPKID, 
        bad.UniquePlayerID, 
        bad.PlayerName, 
        bad.TopPlayerScore, 
        ROW_NUMBER() OVER(ORDER BY TopPlayerScore DESC) AS Position, 
        bad.Date
FROM    (   SELECT  Games.GamePKID, 
                    Games.TopPlayerScore, 
                    TopPlayerScores.ID,  
                    TopPlayerScores.UserPKID, 
                    UserDetails.UniquePlayerID, 
                    UserDetails.PlayerName, 
                    TopPlayerScores.Date,
                    ROW_NUMBER(PARTITION BY UserDetails.UserPKID 
                                ORDER BY Games.TopPlayerScore DESC,
                                        TopPlayerScores.Date DESC) AS RN
            FROM    Games
                    INNER JOIN TopPlayerScores
                        ON TopPlayerScores.GamePKID = Games.GamePKID
                    INNER JOIN UserDetails
                        ON UserDetails.UserPKID = TopPlayerScores.UserPKID
            WHERE   UserDetails.SECRET_DETAIL = 0 
        ) AS bad
WHERE   bad.RN = 1
ORDER BY bad.TopPlayerScore DESC, bad.Date ASC;

使用 ROW_NUMBER() 的 SQL Fiddle 示例

于 2013-06-21T07:53:35.977 回答