好的,这是一个关于索引和子查询的简单问题。我正在使用MariaDB 5.5.36 + MyISAM,这是leaderboard
的表格结构。它包含大约5000万行,大约2000个级别。
int userid,
int levelid,
int score,
index (userid, levelid),
index (levelid, score)
此查询旨在返回给定用户级别中每个分数的排名,运行速度非常慢......
SELECT levelid, (
SELECT COUNT(*) + 1
FROM leaderboard
WHERE score > l.score AND levelid = l.levelid
) AS rank
FROM leaderboard AS l
WHERE userid = 12345;
我也尝试过使用自联接组方法,这种方法在上面运行的时间只有一半,但仍然慢得令人无法接受:
SELECT x.levelid, COUNT(y.score) AS rank
FROM leaderboard AS x
LEFT JOIN leaderboard AS y ON x.levelid = y.levelid AND y.score > x.score
WHERE x.userid = {0}
GROUP BY x.levelid;
...虽然这个替代方案的运行速度提高了大约100倍(伪代码,在数据库外部的应用程序或存储过程中循环结果,然后使用常量分别运行子查询2000次):
results = execute(""SELECT levelid, score
FROM leaderboard
WHERE userid = 12345"");
for each row in results:
execute(""SELECT COUNT(*) + 1
FROM leaderboard
WHERE score > %d AND levelid = %d
"".printf(row.score, row.levelid));
EXPLAIN告诉我,慢速示例中的子查询有一个4字节的key_len(只是levelid),而快速版本使用8(levelid,score)。有趣的是,如果“score> l.score”被替换为“score = l.score”,它会切换到使用全部8,但显然这并没有给我我正在寻找的答案。
有什么我不了解指数从根本上如何运作?有没有更好的方法来编写这个排名查询?将rank
列添加到我的表并在每次达到高分时更新它(这可能意味着更新多达400k行以获得一个单独的分数)会更有效吗?