【问题标题】:MySQL: Slow queries using "group by" - stuck in "Copying to tmp table"MySQL:使用“group by”的慢查询 - 卡在“复制到 tmp 表”中
【发布时间】:2012-02-12 04:21:38
【问题描述】:

我正在为一个与体育赛事相关的网络应用程序制作“排行榜”,该应用程序根据他们对多项选择测验的所有回答的得分报告前 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 过滤。此外,并非所有问题在回答时都有正确答案。正确的答案可能会在未来的某个时间分配,但无论如何要在体育赛事结束时分配。系统报告选择每个答案的用户百分比。因此,人们考虑了各种以更汇总的方式存储分数的方法,但由于它们与这些约束中的一个或多个冲突,因此被丢弃了。

希望这足以继续 - 非常感谢

【问题讨论】:

    标签: mysql sql performance


    【解决方案1】:

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

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

    你是怎么解决这个问题的?

    1. 创建一个与汇总查询结果具有相同列的汇总表。
    2. 创建一个存储过程以从您的详细信息表中提取汇总数据并重写汇总表。
    3. 创建一个事件以在适当的时间间隔运行您的存储过程。你的排行榜显示有多陈旧?六秒、一分钟、一小时?这就是您的活动应该运行的频率。您的问题不是排行榜提取查询的基本成本。问题在于试图每分钟运行无数次。
    4. 重写您的排行榜显示以从汇总表中提取内容。

    这样你就为每个人做一次困难的事情,为每个用户做简单的事情。

    这将稳定您的应用程序并让它很好地扩展。

    【讨论】:

    • 谢谢你;感谢您的回复。我考虑过实现一些类似的东西,但是当重新填充汇总表时出现排行榜请求时会发生什么?
    • 你在使用 InnoDB 吗?如果是这样,您的更新查询将锁定您的汇总表,并且用户请求将挂起大约一秒钟以生成它,然后正常完成。如果您使用的是 MyISAM,您的存储过程可能应该显式锁定汇总表以获得相同的效果。如果这一切都带来了一个可怕的问题,您可以尝试创建一个新表,然后锁定旧表,将其删除,然后将新表重命名为旧表的名称。但这完全是一个需要调试的毛球。
    【解决方案2】:

    看看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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-02-13
      • 1970-01-01
      • 1970-01-01
      • 2013-05-30
      • 2012-08-21
      相关资源
      最近更新 更多