Warning: file_get_contents(/data/phpspider/zhask/data//catemap/1/php/290.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Php 如何优化MySQL游戏排行榜-大量子查询问题_Php_Mysql_Sql_Performance_Query Optimization - Fatal编程技术网

Php 如何优化MySQL游戏排行榜-大量子查询问题

Php 如何优化MySQL游戏排行榜-大量子查询问题,php,mysql,sql,performance,query-optimization,Php,Mysql,Sql,Performance,Query Optimization,我在我的游戏服务器上有一个巨大的瓶颈,用于存储当前排行榜的以下查询 我目前只通过cron每5分钟调用一次此查询,但我希望能够对其进行充分优化,以便在需要时每分钟调用一次 查询耗时30秒,目前只有约2000名用户和7000个游戏(存储在games和TopLayersCore中)。恐怕情况只会变得更糟!! 请帮我搞定克诺比!你是我唯一的希望 SET @rank=0; INSERT INTO Board (TopScorePKID, GamePKID, UserPKID, UniquePlayerID

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

我目前只通过cron每5分钟调用一次此查询,但我希望能够对其进行充分优化,以便在需要时每分钟调用一次

查询耗时30秒,目前只有约2000名用户和7000个游戏(存储在games和TopLayersCore中)。恐怕情况只会变得更糟!! 请帮我搞定克诺比!你是我唯一的希望

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 table type possible_keys key key_len ref rows filtered Extra 1 PRIMARY ALL NULL NULL NULL NULL 1521 100.00 2 DERIVED l ALL NULL NULL NULL NULL 6923 100.00 Using temporary; Using filesort 2 DERIVED u eq_ref PRIMARY PRIMARY 4 DBNAME.l.UserPKID 1 100.00 Using where 2 DERIVED k eq_ref PRIMARY PRIMARY 4 DBNAME.l.GamePKID 1 100.00 Using where 3 DEPENDENT SUBQUERY t ALL NULL NULL NULL NULL 6923 100.00 Using where; Using temporary; Using filesort 3 DEPENDENT SUBQUERY g eq_ref PRIMARY PRIMARY 4 DBNAME.t.GamePKID 1 100.00 Using where 使用以下命令将用户链接到游戏并存储时间/日期

`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`)
)

我要注意的第一件事是,尽管这不会提高性能,但您使用的连接语法在20多年前被ANSI 92 expcict连接语法所取代,这当然是完全受限制的,但有一些非常好的理由可以切换到较新的语法

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

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

但是,您包含的一些列(
g.GamePKID
g.TopPlayerScore
l.ID
l.Date
)不满足每个组的条件,因此,如上所述,MySQL可以自由选择它喜欢的值,即使您有
按g.TopPlayerScore DESC排序,日期ASC
这不会影响MySQL选择的每个组的单行

第三,这可能会妨碍绩效。如果您可以将这些更改为联接,您应该会看到性能改进

考虑到所有这些,我会将您的查询改写为:

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;

子查询
MaxScore
应具有将结果限制为每位玩家一行(仅限其最高分数)的效果,尽管可能需要额外的逻辑来处理平局(例如,玩家在多个游戏中具有相同的上核)。如果不知道确切的要求,我无法纠正这一点

编辑

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

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;


注意:如果MySQL引入了分析函数,如
ROW_NUMBER()
,或者如果您切换到已经支持它们的DBMS,则此查询将变得更加简单。因此,为了防止发生上述任何一种情况,这里有一个使用ROW_NUMBER()的解决方案`


我要注意的第一件事是,尽管这不会提高性能,但您使用的连接语法在20多年前已被ANSI 92 EXPCIT连接语法所取代,这当然是完全受限制的,但有一些非常好的理由可以切换到较新的语法

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

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

但是,您包含的一些列(
g.GamePKID
g.TopPlayerScore
l.ID
l.Date
)不满足每个组的条件,因此,如上所述,MySQL可以自由选择它喜欢的值,即使您有
按g.TopPlayerScore DESC排序,日期ASC
这不会影响MySQL选择的每个组的单行

第三,这可能会妨碍绩效。如果您可以将这些更改为联接,您应该会看到性能改进

考虑到所有这些,我会将您的查询改写为:

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;

子查询
MaxScore
应具有将结果限制为每位玩家一行(仅限其最高分数)的效果,尽管可能需要额外的逻辑来处理平局(例如,玩家在多个游戏中具有相同的上核)。如果不知道确切的要求,我无法纠正这一点

编辑

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

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;


注意:如果MySQL引入分析函数,例如
ROW_NUMBER()
,或者如果您切换到已经支持它们的DBMS,则此查询将变得更加简单。因此,为了防止发生上述任何一种情况,这里有一个使用