Php 如何优化MySQL游戏排行榜-大量子查询问题
我在我的游戏服务器上有一个巨大的瓶颈,用于存储当前排行榜的以下查询 我目前只通过cron每5分钟调用一次此查询,但我希望能够对其进行充分优化,以便在需要时每分钟调用一次 查询耗时30秒,目前只有约2000名用户和7000个游戏(存储在games和TopLayersCore中)。恐怕情况只会变得更糟!! 请帮我搞定克诺比!你是我唯一的希望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
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,则此查询将变得更加简单。因此,为了防止发生上述任何一种情况,这里有一个使用