【问题标题】:Slow query with joined derived tables连接派生表的慢查询
【发布时间】:2016-01-05 12:16:11
【问题描述】:

我在我的应用程序的“自定义仪表板”上有几个查询,其中一个查询需要 10-12 秒才能执行。使用 EXPLAIN 我可以看到为什么它很慢,但我不知道该怎么做。这是查询:

SELECT person.PersonID,FullName,Furigana,qualdate FROM person
  INNER JOIN (
    SELECT pq.PersonID,MAX(ContactDate) AS qualdate FROM person pq
    INNER JOIN contact cq ON pq.PersonID=cq.PersonID
    WHERE cq.ContactTypeID IN (22,26,45) GROUP BY pq.PersonID
  ) qual ON person.PersonID=qual.PersonID
  LEFT OUTER JOIN (
    SELECT pe.personID,MAX(ContactDate) AS elimdate FROM person pe
    INNER JOIN contact ce ON pe.PersonID=ce.PersonID WHERE ce.ContactTypeID IN (25,31,30,41,23,42,2,33,35,29,12)
    GROUP BY pe.PersonID
  ) elim ON qual.PersonID=elim.PersonID
  LEFT OUTER JOIN (
    SELECT po.personID FROM person po
    INNER JOIN percat pc ON po.PersonID=pc.PersonID WHERE pc.CategoryID=38
  ) overseas ON qual.PersonID=overseas.PersonID
  WHERE (elimdate IS NULL OR qualdate > elimdate)
  AND qualdate < CURDATE()-INTERVAL 7 DAY
  AND overseas.PersonID IS NULL
  ORDER BY qualdate

这是 EXPLAIN 结果:

id  select_type  table        type    possible_keys           key         key_len  ref                      rows  Extra
1   PRIMARY      <derived2>   ALL     NULL                    NULL        NULL     NULL                     5447  Using where; Using temporary; Using filesort
1   PRIMARY      <derived3>   ALL     NULL                    NULL        NULL     NULL                     5565  Using where
1   PRIMARY      <derived4>   ALL     NULL                    NULL        NULL     NULL                     9     Using where; Not exists
1   PRIMARY      person       eq_ref  PRIMARY                 PRIMARY     4        qual.PersonID            1     
4   DERIVED      pc           ref     PRIMARY,CategoryID      CategoryID  4                                 8     
4   DERIVED      po           eq_ref  PRIMARY                 PRIMARY     4        kizuna_misa.pc.PersonID  1     Using index
3   DERIVED      pe           index   PRIMARY                 PRIMARY     4        NULL                     5964  Using index
3   DERIVED      ce           ref     PersonID,ContactTypeID  PersonID    4        kizuna_misa.pe.PersonID  1     Using where
2   DERIVED      pq           index   PRIMARY                 PRIMARY     4        NULL                     5964  Using index
2   DERIVED      cq           ref     PersonID,ContactTypeID  PersonID    4        kizuna_misa.pq.PersonID  1     Using where

我确定 EXPLAIN 的第一行揭示了问题(与类似的查询相比,第二行似乎并不太慢),但我不知道如何解决它。我已经在连接中出现的每一列都有索引,但由于表是&lt;derived2&gt; 等,我猜索引是无关紧要的。

目标(因为对于不熟悉我的应用程序和架构的人来说可能并不明显)是一个后续的痒痒列表 - 如果发生了 #22/26/45 联系人之一但没有做任何响应(其中之一其他几个联系人或通过类别分配指定此人在海外),则此人应在等待一周后出现在列表中以进行跟进。子查询比这些杂乱的连接更容易编写和理解,但我无法检查日期的顺序(而且子查询通常也很慢)。

编辑(回应 Rick James):

MySQL 版本是 5.0.95(是的,我知道...)。这里是所涉及的三个表的SHOW CREATE TABLE,尽管person 中的大部分字段都无关紧要:

CREATE TABLE `contact` (
 `ContactID` int(11) unsigned NOT NULL auto_increment,
 `PersonID` int(11) unsigned NOT NULL default '0',
 `ContactTypeID` int(11) unsigned NOT NULL default '0',
 `ContactDate` date NOT NULL default '0000-00-00',
 `Description` text,
 PRIMARY KEY  (`ContactID`),
 KEY `ContactDate` (`ContactDate`),
 KEY `PersonID` (`PersonID`),
 KEY `ContactTypeID` (`ContactTypeID`)
) ENGINE=MyISAM AUTO_INCREMENT=16901 DEFAULT CHARSET=utf8

CREATE TABLE `percat` (
 `PersonID` int(11) unsigned NOT NULL default '0',
 `CategoryID` int(11) unsigned NOT NULL default '0',
 PRIMARY KEY  (`PersonID`,`CategoryID`),
 KEY `CategoryID` (`CategoryID`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8

CREATE TABLE `person` (
 `PersonID` int(11) unsigned NOT NULL auto_increment,
 `FullName` varchar(100) NOT NULL default '',
 `Furigana` varchar(100) NOT NULL default '',
 `Sex` enum('','M','F') character set ascii NOT NULL default '',
 `HouseholdID` int(11) unsigned NOT NULL default '0',
 `Relation` varchar(6) character set ascii NOT NULL default '',
 `Title` varchar(6) NOT NULL default '',
 `CellPhone` varchar(30) character set ascii NOT NULL default '',
 `Email` varchar(70) character set ascii NOT NULL default '',
 `Birthdate` date NOT NULL default '0000-00-00',
 `Country` varchar(30) NOT NULL default '',
 `URL` varchar(150) NOT NULL default '',
 `Organization` tinyint(1) NOT NULL default '0',
 `Remarks` text NOT NULL,
 `Photo` tinyint(1) NOT NULL default '0',
 `UpdDate` date NOT NULL default '0000-00-00',
 PRIMARY KEY  (`PersonID`),
 KEY `Furigana` (`Furigana`),
 KEY `FullName` (`FullName`),
 KEY `Email` (`Email`),
 KEY `Organization` (`Organization`,`Furigana`)
) ENGINE=MyISAM AUTO_INCREMENT=6063 DEFAULT CHARSET=utf8

尝试的建议:

我尝试执行 Rick James 将子选择放入字段列表的建议(我什至不知道这是可能的),如下所示:

SELECT
  p.PersonID,
  FullName,
  Furigana,
  (SELECT MAX(ContactDate) FROM contact cq
    WHERE cq.PersonID=p.PersonID
    AND  cq.ContactTypeID IN (22,26,45))
  AS qualdate,
  (SELECT MAX(ContactDate) FROM contact ce
    WHERE ce.PersonID=p.PersonID
    AND ce.ContactTypeID IN (25,31,30,41,23,42,2,33,35,29,12))
  AS elimdate
FROM person p
WHERE (elimdate IS NULL OR qualdate > elimdate)
AND qualdate < CURDATE()-INTERVAL 7 DAY
AND NOT EXISTS (SELECT * FROM percat WHERE CategoryID=38 AND percat.PersonID=p.PersonID)
ORDER BY qualdate

但它抱怨:#1054 - Unknown column 'elimdate' in 'where clause' 根据文档,WHERE 子句在字段列表之前解释,所以这种方法行不通。

【问题讨论】:

    标签: mysql performance join explain


    【解决方案1】:

    您有一个有趣的问题。我不确定最好的解决方案是什么。这里有两个猜测:

    A计划

    INDEX(qualdate)
    

    可能会有所帮助。请提供SHOW CREATE TABLE。

    这个结构优化很差:

    FROM ( SELECT ... )
    JOIN ( SELECT ... )
    

    在您的情况下,overseas 可能应该变成JOIN,而不是子选择。而另外两个可能应该变成不同风格的依赖子查询:

    SELECT  ..., 
            ( SELECT MAX(...) ... )  AS qualdate,
            ( SELECT MAX(...) ... )  AS elimdate
        FROM ...
    

    你运行的是什么版本的 MySQL?

    B计划

    如果可行,请将它们折叠到子查询中,以便它们生成更少的行,从而减少外部查询的工作量。 (每个子查询一个)

    elimdate IS NOT NULL
    qualdate < CURDATE()-INTERVAL 7 DAY
    overseas.PersonID IS NOT NULL
    

    也许NULL 测试适用于LEFT,而这个建议可能不适用。

    【讨论】:

    • Plan A:(1)如何在一个实际上不存在的字段(子查询的结果)上建立索引? (2) 请参阅我编辑的问题,了解我是否尝试按照您的建议进行操作。计划B:我不明白你在说什么——我不能在子查询中使用子查询的结果。我还使用您要求的其他信息编辑了我的问题。
    • 首先删除WHERE (elimdate IS NULL OR qualdate &gt; elimdate),看看它是否有效。如果不需要,我们可以讨论是否需要在子查询中添加某些内容,或者是否需要在外部添加 HAVING。
    • 我不确定你的意思是什么,“...看看它是否有效。”正如我所料,MySQL 直到我删除了那一行和下一行 (AND qualdate &lt; CURDATE()-INTERVAL 7 DAY) 才停止抱怨,并且删除这两行自然破坏了这个查询的全部目的——执行并返回了 5,957 行,而正确的数字应该是 87。
    • 抱歉,我应该建议删除(elimdate IS NULL OR qualdate &gt; elimdate) AND。
    • 是的,我知道 - 我确实保持语法正确。但只要在 WHERE 子句中有任何对 elimdate 或 qualdate 的引用,它就会抱怨它是一个未知列,因为在解释 WHERE 时,这两个别名还不存在。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-12-28
    • 2016-09-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多