【问题标题】:GROUP BY query optimizationGROUP BY 查询优化
【发布时间】:2011-08-29 15:16:26
【问题描述】:

数据库是带有 MyISAM 引擎的 MySQL。

表定义:

CREATE TABLE IF NOT EXISTS  matches  (
   id  int(11) NOT NULL AUTO_INCREMENT,
   game  int(11) NOT NULL,
   user  int(11) NOT NULL,
   opponent  int(11) NOT NULL,
   tournament  int(11) NOT NULL,
   score  int(11) NOT NULL,
   finish  tinyint(4) NOT NULL,
  PRIMARY KEY ( id ),
  KEY  game  ( game ),
  KEY  user  ( user ),
  KEY  i_gfu ( game , finish , user )
) ENGINE=MyISAM  DEFAULT CHARSET=latin1 AUTO_INCREMENT=3149047 ;

我已经在(game, finish, user) 上设置了一个索引,但是这个GROUP BY 查询仍然需要 0.4 - 0.6 秒才能运行:

SELECT user AS player
     , COUNT( id ) AS times
FROM matches
WHERE finish = 1
  AND game = 19
GROUP BY user
ORDER BY times DESC

EXPLAIN 输出:

| id | select_type | table   | type | possible_keys | key   | key_len | 
|  1 |  SIMPLE     | matches |  ref | game,i_gfu    | i_gfu |    5    | 

|  ref        |   rows |   Extra                                      |
| const,const | 155855 | Using where; Using temporary; Using filesort |

有什么方法可以让它更快吗?该表有大约 80 万条记录。


编辑:我将COUNT(id) 更改为COUNT(*),时间下降到 0.08 - 0.12 秒。我想我在创建索引之前已经尝试过了,但之后又忘记了。

在解释输出中,Using index解释了加速:

|   rows |   Extra                                                   |
| 168029 | Using where; Using index; Using temporary; Using filesort |

(附带问题:下降 5 倍正常吗?)

大约有2000个用户,所以最终的排序,即使使用filesort,也不会影响性能。我尝试不使用ORDER BY,但仍然需要几乎相同的时间。

【问题讨论】:

  • count(*) 的性能比 count(id) 快得多的原因是 MySQL 对 count(*) 情况进行了特定优化。 count(id) 案例第二次遍历数据以检索结果,其中 count(*) 使用现有的内部行计数器。尽可能使用 count(*)。

标签: mysql group-by query-optimization myisam


【解决方案1】:

摆脱“游戏”键 - 它与“i_gfu”是多余的。由于 'id' 是唯一的 count(id) 只返回每个组中的行数,因此您可以摆脱它并用 count(*) 替换它。尝试这种方式并粘贴 EXPLAIN 的输出:

SELECT user AS player, COUNT(*) AS times
FROM matches
WHERE finish = 1
AND game = 19
GROUP BY user
ORDER BY times DESC

【讨论】:

    【解决方案2】:

    嗯,难。尝试重新排序您的索引:将user 列放在第一位(因此将索引设为(user, finish, game)),因为这会增加 GROUP BY 可以使用索引的机会。但是,一般来说,如果您将聚合函数限制为 MIN 和 MAX,则 GROUP BY 只能使用索引(请参阅 http://dev.mysql.com/doc/refman/5.0/en/group-by-optimization.html 和 http://dev.mysql.com/doc/refman/5.5/en/loose-index-scan.html)。您的订购也没有真正的帮助。

    【讨论】:

    • 我已经尝试过该索引以及(user, game, finish) 并强制使用它,但它甚至更慢。
    • 奇数。我感觉你不能通过组合 GROUP BY 和 ORDER BY 做得更好:如果查询速度太慢,你可能想要创建一个显式聚合表。 Using filesort 显示的事实表明 ORDER BY 无法从任何索引完成:也许尝试将 id 添加到索引?
    • 您的意思是(game, finish, user, id) 索引?
    • 好吧,我会说试试看尺寸,但如果使用 COUNT(*) 有帮助,那可能不会有多大好处。
    【解决方案3】:

    EXPLAIN 验证查询中使用了(game, finish, user) 索引。对我来说,这似乎是最好的索引。会不会是硬件问题?您的系统 RAM 和 CPU 是多少?

    【讨论】:

    • 内存为 1GB。 CPU 是(我认为)AMD Opteron 四核 3.5GHz。
    • 我猜你的瓶颈是内存。我建议将其增加到 4GB。
    • 4Gb 来处理 900k 行 ~30 字节的表? ;) 这甚至不是 30 兆字节;)
    • @lucek 您的数学是正确的,但是这些天操作系统开销会占用大量 RAM。此外,任何其他正在运行的应用程序都将消耗 RAM。如今,4GB 几乎是标准配置。
    • @ypercube 这里可能有一个基于软件的建议,可以为您加快速度。你的表、索引和 SQL 结构对我来说似乎很好,所以我怀疑那里的任何调整都会有所帮助。 @Thomas Jones-Low 关于服务器变量的建议可能会有所帮助。如果似乎没有任何帮助,那么额外几 GB 的 RAM 是相当便宜的。
    【解决方案4】:

    我认为大部分时间都花在了 800k 行中的 150k 行中提取和更重要的排序(两次,包括通过读取索引跳过的那一次)。我怀疑你可以比现在优化它更多。

    【讨论】:

    • 提取,是的。排序不,它不花时间排序。
    • 这不是您的查询计划所建议的。就此而言,也不是您的查询。他们都说至少需要一种。 :-)
    • 我的意思是,它花在排序上的时间与花在分组上的时间相比非常短。
    • 我也不能怪它这样做......它根据您的查询计划将许多行(您的表的一半?)分组为 150k 行。 :-)
    • 事实上,我 99% 确定您在浪费时间尝试优化它:您当前的三列索引允许直接进入颈静脉,如获取相关行并将它们按原样分组。然后需要对它们进行分类,这也需要时间。我非常诚实地看到你可以做的其他事情。如果有的话,我真的很惊讶规划器决定使用索引,因为您正在检索 20% 的表。
    【解决方案5】:

    正如其他人所指出的,您调整查询本身的能力可能已达到极限。接下来您应该看到服务器中max_heap_table_size 和tmp_table_size 变量的设置。默认值为 16MB,这对于您的表来说可能太小了。

    【讨论】:

    • 谢谢建议,两个设置都是64M。
    【解决方案6】:

    此查询的一个缺点是您按聚合排序。这意味着在生成完整的结果集之前,您不能返回任何行;没有索引可以存在(对于mysql myisam,无论如何)来解决这个问题。

    不过,您可以相当容易地对数据进行非规范化来克服这个问题;例如,您可以添加一个插入/更新触发器以将计数值粘贴到带有索引的汇总表中,以便您可以立即开始返回行。

    【讨论】:

      猜你喜欢
      • 2012-12-31
      • 1970-01-01
      • 1970-01-01
      • 2020-05-16
      • 2020-08-23
      • 2019-08-16
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多