Warning: file_get_contents(/data/phpspider/zhask/data//catemap/8/mysql/61.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
MySQL:避免GROUPBY子句导致的临时/Filesort_Mysql_Sql - Fatal编程技术网

MySQL:避免GROUPBY子句导致的临时/Filesort

MySQL:避免GROUPBY子句导致的临时/Filesort,mysql,sql,Mysql,Sql,我有一个相当简单的查询,它试图显示已订阅的电子邮件地址数和未订阅的电子邮件地址数,并按客户端分组 查询: SELECT client_id, COUNT(CASE WHEN subscribed = 1 THEN subscribed END) AS subs, COUNT(CASE WHEN subscribed = 0 THEN subscribed END) AS unsubs FROM contacts_emailAddresses LEFT JOIN c

我有一个相当简单的查询,它试图显示已订阅的电子邮件地址数和未订阅的电子邮件地址数,并按客户端分组

查询:

SELECT
    client_id,
    COUNT(CASE WHEN subscribed = 1 THEN subscribed END) AS subs,
    COUNT(CASE WHEN subscribed = 0 THEN subscribed END) AS unsubs
FROM
    contacts_emailAddresses
LEFT JOIN contacts ON contacts.id = contacts_emailAddresses.contact_id
GROUP BY
    client_id
相关表的模式如下。contacts_emailAddresses是contacts(具有客户端id)和emailAddresses(在此查询中未实际使用)之间的连接表

下面是解释:

+----+-------------+-------------------------+--------+---------------+---------+---------+-------------------------------------------+----------+---------------------------------+
| id | select_type | table                   | type   | possible_keys | key     | key_len | ref                                       | rows     | Extra                           |
+----+-------------+-------------------------+--------+---------------+---------+---------+-------------------------------------------+----------+---------------------------------+
| 1  | SIMPLE      | contacts_emailAddresses | ALL    | NULL          | NULL    | NULL    | NULL                                      | 10176639 | Using temporary; Using filesort |
| 1  | SIMPLE      | contacts                | eq_ref | PRIMARY       | PRIMARY | 4       | icarus.contacts_emailAddresses.contact_id | 1        |                                 |
+----+-------------+-------------------------+--------+---------------+---------+---------+-------------------------------------------+----------+---------------------------------+
2 rows in set (0.08 sec)
这里的问题显然是GROUPBY子句,因为我可以删除连接(以及依赖它的项),而性能仍然很糟糕(40秒以上)。联系人电子邮件地址中有1000万条记录,联系人中有1200万条记录,分组中有10-15条客户记录

从:

可以在以下条件下创建临时表:

如果存在ORDER BY子句和其他GROUP BY子句,或者ORDER BY或GROUP BY包含联接队列中第一个表以外的表中的列,则会创建一个临时表

DISTINCT与ORDERBY组合可能需要一个临时表

如果使用SQL\u SMALL\u RESULT选项,MySQL将使用内存中的临时表,除非查询还包含需要磁盘存储的元素(稍后描述)

我显然没有将GROUP BY与ORDER BY组合在一起,我尝试了多种方法来确保GROUP BY位于应该正确放置在加入队列中的列上(包括重写查询以将联系人放入FROM中,而不是加入到contacts_emailAddresses中),但都没有用


如果您对性能调整有任何建议,我们将不胜感激

我认为摆脱“使用临时;使用文件排序”操作(给定当前模式、当前查询和指定的结果集)的唯一方法是在选择列表中使用相关子查询

SELECT c.client_id
     , (SELECT IFNULL(SUM(es.subscribed=1),0)
          FROM contacts_emailAddresses es
          JOIN contacts cs
            ON cs.id = es.contact_id
         WHERE cs.client_id = c.client_id
       ) AS subs
     , (SELECT IFNULL(SUM(eu.subscribed=0),0)
          FROM contacts_emailAddresses eu
          JOIN contacts cu
            ON cu.id = eu.contact_id
         WHERE cu.client_id = c.client_id
       ) AS unsubs
  FROM contacts c
 GROUP BY c.client_id
这可能会比原始查询运行得更快,也可能不会。这些相关子查询将针对外部查询返回的每个子查询运行。如果外部查询返回大量的行,那就是大量的子查询执行

下面是一个
解释的输出:



为了优化此查询的性能,我们非常希望在explain的额外列中为
eu
es
表看到“使用索引”。但要做到这一点,我们需要一个合适的索引,它的前导列是
contact\u id
,包括
subscribed
列。例如:

CREATE INDEX cemail_IX2 ON contacts_emailAddresses (contact_id, subscribed);
新索引可用时,
EXPLAIN
输出显示MySQL将使用新索引:



注释

这就是引入少量冗余可以提高性能的问题。(就像我们在传统数据仓库中所做的那样。)

为了获得最佳性能,我们真正想要的是在
contacts\u emailAddresses
表中提供
client\u id
列,而无需加入contacts表

在当前模式中,与
contacts
表的外键关系为我们获得
客户机id
(相反,原始查询中的连接操作是我们获得它的原因)。如果我们可以完全避免该连接操作,我们可以完全从单个索引满足查询,使用索引进行聚合,并避免了“使用临时;使用文件排序”和联接操作的开销

使用可用的
client\u id
列,我们将创建一个覆盖索引,如

... ON contacts_emailAddresses (client_id, subscribed)
然后,我们会有一个非常快速的查询

SELECT e.client_id
     , SUM(e.subscribed=1) AS subs
     , SUM(e.subscribed=0) AS unsubs
  FROM contacts_emailAddresses e
GROUP BY e.client_id
这将在查询计划中为我们提供一个“使用索引”,而这个结果集的查询计划没有比这更好的了

但是,这需要更改您的scheam,它并不能真正回答您的问题



如果没有
client_id
列,那么我们可能会做的最好的查询就是像Gordon在其答案中发布的查询(尽管您仍然需要按c.client_id
添加
组以获得指定的结果)。Gordon推荐的索引将是有益的

... ON contacts_emailAddresses(contact_id, subscribed)
定义了该索引后,联系人id上的独立索引是多余的。新索引将是支持现有外键约束的合适替代品。(仅
联系人id
上的索引可能会被删除。)


另一种方法是在执行联接之前,首先在“大”表上进行聚合,因为它是外部联接的驱动表。实际上,由于外键列被定义为NOTNULL,并且有一个外键,所以它实际上根本不是一个“外部”连接

SELECT c.client_id
     , SUM(s.subs) AS subs
     , SUM(s.unsubs) AS unsubs 
  FROM ( SELECT e.contact_id
              , SUM(e.subscribed=1) AS subs
              , SUM(e.eubscribed=0) AS unsubs
           FROM contacts_emailAddresses e
          GROUP BY e.contact_id
       ) s
 JOIN contacts c
   ON c.id = s.contact_id
GROUP BY c.client_id

同样,为了获得最佳性能,我们需要一个以
contact_id
为前导列并包含
subscribed
列的索引。(针对
s
的计划应该显示“使用索引”。)不幸的是,这仍然会将相当大的结果集(派生表
s
)具体化为临时MyISAM表,而MyISAM表不会被索引。

感谢您的建议!虽然方法1和3(您的“相关子查询”和“大”表上的聚合”建议)产生了比原始方法稍好的性能,但相差不大。正如预期的那样,更改模式以将客户机id(和关联的组合索引)添加到contacts\u emailAddresses表中,产生了迄今为止最佳的性能增益,将查询时间从40多秒缩短到3秒。我需要用我实现的另一种解决方法来权衡模式的变化,即创建一个“stats”表,该表使用来自orig的数据定期更新。query.@jcq:如果不需要截止到小时的结果,如果截止到昨天的结果足够好,那么summary stats表是一种可行的方法。通过添加
client_id
列,该列必须与
contacts
表上的
client_id
保持“同步”。我
SELECT e.client_id
     , SUM(e.subscribed=1) AS subs
     , SUM(e.subscribed=0) AS unsubs
  FROM contacts_emailAddresses e
GROUP BY e.client_id
... ON contacts_emailAddresses(contact_id, subscribed)
SELECT c.client_id
     , SUM(s.subs) AS subs
     , SUM(s.unsubs) AS unsubs 
  FROM ( SELECT e.contact_id
              , SUM(e.subscribed=1) AS subs
              , SUM(e.eubscribed=0) AS unsubs
           FROM contacts_emailAddresses e
          GROUP BY e.contact_id
       ) s
 JOIN contacts c
   ON c.id = s.contact_id
GROUP BY c.client_id