我的游戏服务器上有一个巨大的瓶颈,用于以下查询,用于存储当前的排行榜。
我目前只通过cron每隔5分钟调用一次此查询,但是我希望能够优化它以便每分钟或在需要时调用它。
查询耗时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);
请有人帮忙!我应该把它分解成观点吗?或使用Inner Join关键字?什么是最好的方法?
非常感谢甚至看着这个烂摊子:D!
更新1.0: EXPLAIN EXTENDED结果:
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
更新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`)
)
答案 0 :(得分:4)
我要注意的第一件事,虽然这不会提高性能,但是你使用的JOIN语法在20多年前被ANSI 92 expcict join语法所取代,它完全是主题,但是{{3切换到更新语法的一些非常好的理由。
要注意的第二件事是您的结果将是非确定性的。您正在选择未包含在聚合或组中的列。虽然MySQL允许这样做,但您没有像MySQL那样使用该功能。 Aaron Bertrand explains州:
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 docs这可能会影响性能。如果您可以将这些更改为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;
<强> MySQL has limitations with correlated subqueries 强>
子查询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;
<强> Example on SQL Fiddle 强>
N.B。如果/当MySQL引入诸如ROW_NUMBER()
之类的分析函数,或者如果你切换到已经支持它们的DBMS,这个查询将变得更加简单,所以为了防止这些事情发生,这里有一个使用ROW_NUMBER的解决方案( )`的
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;
<强> Example on SQL Fiddle 强>