MySQL:使用“分组依据”缓慢查询 - 卡在“复制到tmp表”中

时间:2012-01-17 10:50:53

标签: mysql sql performance

我正在制作一个"排行榜"用于与体育赛事相关的网络应用程序,该应用程序根据他们对多项选择测验的所有回答的分数来报告前20名用户。它还显示当前用户在排行榜中的排名。

当对此应用程序进行负载测试时,相关的两个查询变得非常缓慢,花费大量时间在"复制到tmp表"状态(每个查询最多20秒)。它们最终会进行处理,但与此同时,数百个可以叠加。

在响应表中给定合理数量的行时,每个查询执行大约需要1秒(25K用户,例如响应中的200K行)

我已经为相关表添加了一些索引,特别是对于FK列和where语句中使用的任何内容。我还在响应表上为userID,answerID添加了覆盖索引。

这是排行榜本身的查询

SELECT users.username, sum(questions.points) as score FROM responses
JOIN answers on responses.answerID = answers.answerID
JOIN questions on answers.questionID = questions.questionID
JOIN users on responses.userID = users.userID
WHERE users.username != '' AND answers.isCorrect  
GROUP BY users.userID
ORDER BY score DESC
LIMIT 20

这是在结果中获得用户自己排名的查询;单独的查询首先获得他们的分数,然后我们计算有多少用户获得更高的分数。

Select count(*) +1 as rank  from (
    SELECT users.username, sum(questions.points) as score
    FROM responses
    JOIN answers on responses.answerID = answers.answerID
    JOIN questions on answers.questionID = questions.questionID
    JOIN users on responses.userID = users.userID
    WHERE users.username != '' AND answers.isCorrect 
    GROUP BY users.userID
    HAVING sum(questions.points) > 2431
    ORDER BY score DESC
) as result

简化架构

QUESTIONS
questionID
question
points

ANSWERS (multiple choice answers for question)
answerID
questionID
answer
isCorrect

RESPONSES (the player's choice of answer)
responseID
answerID
userID

我认为这些查询是以一种模糊的方式进行的,但我想知道是否有一种明显更好的方法来做其中任何一种我没有考虑过的方法。

此外,是否有人对这些查询为什么会叠加在"复制到tmp表"当服务器负载时,只需要很长时间才能处理?我认为它可能是在磁盘上创建它们但我看到这是一个单独的状态消息。我使用了EXPLAIN,但我的感觉是这些查询不可避免地存在临时表;因为"复制到tmp表"花了这么长时间

约束:未显示,用户拥有teamID,查询也由teamID过滤。同样未显示,有几个事件,这些查询也可以通过eventID进行过滤。此外,并非所有问题在他们回答时都有正确的答案。可以在未来的某个时间点分配正确的答案,但无论如何在体育赛事结束时分配。系统报告选择每个答案的用户百分比。因此,已经考虑了以更加聚合的方式存储分数的各种方式,但是因为它们与这些约束中的一个或多个相冲突而被丢弃。

希望这已经足够了 - 非常感谢

2 个答案:

答案 0 :(得分:2)

我做过这样的事情,也有类似的问题。同时查询会堆积起来,因为它们需要序列化,因此每个查询都会在运行时返回正确的结果。

有利于您在负载测试中捕获它,而不是在生产中。

你是如何解决这个问题的?

  1. 创建一个与摘要查询结果具有相同列的摘要表。
  2. 创建存储过程以从详细信息表中提取摘要数据并重写摘要表。
  3. 创建一个事件以适当的间隔运行存储过程。您的排行榜显示有多陈旧?六秒钟,一分钟,一小时?这就是您的活动应该经常运行的频率。您的问题不是排行榜提取查询的基本成本。问题来自试图每分钟运行数万次。
  4. 重写您的排行榜显示以从摘要表中提取内容。
  5. 通过这种方式,你可以为每个人做一次硬性的事情,并为每个用户提供简单的东西。

    这样可以稳定您的应用程序,让它可以很好地扩展。

答案 1 :(得分:0)

查看http://dev.mysql.com/doc/refman/5.5/en/internal-temporary-tables.html

如果存在ORDER BY子句和不同的GROUP BY子句,并且在其他情况下列出,则MySQL使用临时表。你无法绕过它。

因此,在您的情况下,最简单的解决方案可能是设置RAM磁盘并使MySQL存储临时表,如下所述:

skip copying to tmp table on disk mysql