按顺序优化分组

时间:2015-06-21 02:54:18

标签: mysql performance optimization

我有以下sql查询

SELECT a.player_id, COUNT( a.player_id ) AS views, b.firstname, b.lastname, b.link_id
FROM buyout_calculator_query AS a
LEFT JOIN player AS b ON ( a.player_id = b.player_id ) 
WHERE a.timestamp >259200
GROUP BY player_id
ORDER BY views DESC

我有以下索引,它基于我见过的各种堆栈答案:

CREATE INDEX timestamp_playerid_index ON buyout_calculator_query(
timestamp,
player_id
)

我的错误理解是这会阻止临时表,因为时间戳是约束,按player_id分组,并由player_id求和

但是,在运行explain时,这就是我所看到的(临时表):

Possible Keys: timestamp_playerid_index
Key: timestamp_playerid_index
Extra: Using where; Using index; Using temporary; Using filesort

如何优化此查询并阻止使用临时表?

1 个答案:

答案 0 :(得分:1)

我的答案相当漫长,但我希望你能学到很多东西。我给你两个可能的改进。

“防止使用临时表”和“防止'文件输出'”。这些都不是真正的目标。真正的目标是更快的查询。

GROUP BY one_thing
ORDER BY something_else

总是(我认为)至少需要一个temp和filesort,有时候需要两个。只需要实现你的目标。

另一方面,支持SELECT所需的temp + filesort不一定是基于磁盘的“文件”。它通常只是一个内存中的数据集(实际上是MEMORY表)。

让我们进一步了解你所拥有的东西:

Filter on a.timestamp -- but a "range"
GROUP BY a.player_id
ORDER BY an aggregate -- not know up front, so no way to use an index.

如果优化器按照给定的顺序执行操作,则可以

  1. 使用以timestamp开头的索引进行过滤,并将其写入tmp表
  2. 排序以执行GROUP BY
  3. 再次排序以执行ORDER BY
  4. (我可能对GROUP BY处理工作的方式感到悲观。使用EXPLAIN FORMAT=JSON SELECT...获取更多信息。)

    您建议使用复合INDEX(timestamp, player_id)。好吧,因为第一个部分用于范围,所以没有用。想一想:你有很多人和他们的出生年份。并且你想要所有姓氏以'B'开头并且你想按出生年份分组的人。安排列表的最佳方法是什么,这样你就不会复制东西并对它们进行排序?然后按最常见的出生年份进行排序。

    返回复合索引。作为一般规则,如果您在“范围”上下文中使用索引中的 first 列,则索引的其余部分将不使用。

    因此,给定查询最有用的索引仅为INDEX(timestamp)更正INDEX(timestamp, player_id)更好,因为它是一个“覆盖索引”,因此可以避免进入数据。 EXPLAIN为您提供Using index的线索。

    请为这两个表提供SHOW CREATE TABLE;我不得不从这里猜出来......

    我想playerPRIMARY KEY(player_id),对吗?

    您正在使用LEFT,因为买断查询会引用不存在的玩家?似乎不太可能,所以我猜你没有正当理由加上LEFT

    另外,我猜您没有正当理由说COUNT(a.player_id)而不是COUNT(*)

    一旦摆脱了LEFT,我们就可以尝试另一种查询形式:

    SELECT  b.player_id, 
          ( SELECT  COUNT(*)
                FROM  buyout_calculator_query
                WHERE  player_id = b.player_id
                  AND  timestamp >259200 
          ) AS views,
          b.firstname, b.lastname, b.link_id
        FROM  player AS b
        ORDER BY  views DESC
    

    看看它是否运行得更快。它有一个“相关子查询”,但避免使用GROUP BY。请将其添加到buyout_calculator_queryINDEX(player_id, timestamp)

    更进一步,这可能(或可能不)更好:

    SELECT  b.player_id, a.views, b.firstname, b.lastname, b.link_id
        FROM  
          ( SELECT  player_id, COUNT(*) AS views
                FROM  buyout_calculator_query
                WHERE  timestamp >259200
                GROUP BY  player_id 
          ) AS a
        JOIN  player AS b USING(player_id)
        ORDER BY  a.views DESC 
    

    如果您有INDEX(player_id, timestamp),这将是“使用索引”;通过避免索引和数据之间的弹跳,这是一个额外的提升。此外,子查询不需要tmp表,也不需要filesort。但是子查询生成一个tmp表,ORDER BY需要排序。