如何优化此SQL查询以摆脱filesort和临时表?

时间:2009-11-08 06:08:48

标签: sql mysql database refactoring

以下是查询:

SELECT 
  count(id) AS count 
FROM `numbers` 
GROUP BY 
  MONTH(created_at), 
  YEAR(created_at) 
ORDER BY 
  YEAR(created_at), 
  MONTH(created_at)

在执行EXPLAIN时,该查询会抛出'Using temporary'和'Using filesort'。

最终我正在做的是查看用户提交的跟踪号码表,并计算按月/年分组计数的提交行数。

即。 2008年11月,提交了11,312行。

更新,这是numbers表的DESCRIBE。

id  int(11) NO  PRI NULL    auto_increment
tracking    varchar(255)    YES     NULL    
service varchar(255)    YES     NULL    
notes   text    YES     NULL    
user_id int(11) YES     NULL    
active  tinyint(1)  YES     1   
deleted tinyint(1)  YES     0   
feed    text    YES     NULL    
status  varchar(255)    YES     NULL    
created_at  datetime    YES     NULL    
updated_at  datetime    YES     NULL    
scheduled_delivery  date    YES     NULL    
carrier_service varchar(255)    YES     NULL    

6 个答案:

答案 0 :(得分:1)

试一试:

  SELECT COUNT(x.id)
    FROM (SELECT t.id,
                 MONTH(t.created_at) 'created_month', 
                 YEAR(t.created_at) 'created_year'
            FROM NUMBERS t) x
GROUP BY x.created_month, x.created_year
ORDER BY x.created_month, x.created_year

WHEREGROUP BYORDER BY子句中使用函数不是一个好习惯,因为不能使用索引。

  

...查询在执行EXPLAIN时抛出'Using temporary'和'Using filesort'。

从我found开始,这是使用DISTINCT / GROUP BY时的预期结果。

答案 1 :(得分:0)

SELECT
    count(`id`) AS count, MONTH(`created_at`) as month, YEAR(`created_at`) as year
FROM `numbers`
GROUP BY month, year
ORDER BY created_at

据我所知,这将是你能得到的最好的。我创建了一个带有id和datetime列的表,并用10000行填充它。上面的查询使用了一个子选择,但它实际上没有任何不同,并且具有子选择的开销。由此产生的时间为0.015秒,他的时间为0.016秒。

确保您在created_at上有索引,这有助于初步查询。当分组出现时,很少会以文件排序结束,但在其他情况下可能会出现这种情况。如果您有这种倾向,MySql的文档会有an article。我不知道如何使用您提供的信息在这里应用这些方法。

答案 2 :(得分:0)

确保覆盖索引超过YEAR和MONTH(即同一索引中的两个字段),以便查询的ORDER BY组件可以使用索引。这应该不需要文件排序,尽管可能仍然需要临时表来处理分组。

答案 3 :(得分:0)

每当MySQL必须在内存中工作,并且该工作超过可用量(innodb_buffer_pool_size)时,它就开始使用磁盘来存储临时工作。你可以增加我提到的变量,但设置得太高可能会导致其他方面的性能问题。

如果您正在运行专用服务器,请将其设置为~50-75%。

答案 4 :(得分:0)

最好的方法是创建一个辅助列,其中包含连接在一起的YEARMONTH的数字值:

YEAR(created_at) * 100 + MONTH(created_at)

对此列进行分组将使用INDEX FOR GROUP BY

但是,您可以创建两个辅助表,第一个包含合理的年数(例如,从19002100),第二个包含月份(从011),并使用这些表生成集合:

SELECT  (
        SELECT  COUNT(*)
        FROM    numbers
        WHERE   created_at >= '1900-01-01' + INTERVAL y YEAR + INTERVAL m MONTH
                AND created_at < '1900-01-01' + INTERVAL y YEAR + INTERVAL m + 1 MONTH
        )
FROM    year_table
CROSS JOIN
        month_table
WHERE   y BETWEEN 2008 AND 2010

答案 5 :(得分:0)

对不起,但我不同意其他答案 我认为你需要的是为你的表添加一个索引,最好是covering index

如果您在要搜索的列(created_at)和 上添加一个索引,您想要从(id)获得结果,那么它将比以前快得多。< / p>

您使用临时表的原因是因为您使用了分组 要加快组的速度,可以更改MySQL服务器设置以增加tmp表的大小和最大堆表大小,以便临时表位于内存中。