【问题标题】:How can I optimize this SQL query to get rid of the filesort and temp table?如何优化此 SQL 查询以摆脱文件排序和临时表?
【发布时间】:2009-11-08 06:08:47
【问题描述】:

这是查询:

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

在执行 EXPLAIN 时,该查询会引发“使用临时”和“使用文件排序”。

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

即。 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    

【问题讨论】:

  • 为什么要摆脱它们?
  • numbers发布DESCRIBE TABLE
  • 最终我试图让查询更快。现在它正在查询约 200,000 行并且需要约 500 毫秒。随着行数的增加,显然需要更长的时间。

标签: sql mysql database refactoring


【解决方案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 时会抛出“使用临时”和“使用文件排序”。

根据我 found 的说法,使用 DISTINCT/GROUP BY 时这是可以预料的。

【讨论】:

  • 我假设您执行子查询只是为了减少要计算的结果中的列数?
【解决方案2】:

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

【讨论】:

    【解决方案3】:
    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.015s,他的结果是 0.016s。

    确保您在created_at 上有一个索引,这将有助于您完成初始查询。当 group by 出现时,不以文件排序结束是非常罕见的,但在其他情况下可能是可能的。如果您愿意的话,MySql 的文档有 an article 关于这个。根据您提供的信息,我看不出如何在此处应用这些方法。

    【讨论】:

      【解决方案4】:

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

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

      【讨论】:

        【解决方案5】:

        最好的方法是创建一个辅助列,其中包含连接在一起的 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
        

        【讨论】:

          【解决方案6】:

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

          如果您在要搜索的列 (created_at) 上添加索引,并且在要从 (id) 获得结果的列上添加索引,那么它会比以前快得多。

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

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2011-06-23
            • 1970-01-01
            • 2014-02-13
            • 1970-01-01
            相关资源
            最近更新 更多