加入派生表

时间:2015-10-08 03:42:02

标签: mysql performance join explain

我在应用程序的“自定义仪表板”上有一些查询,其中一个查询需要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的第一行显示问题(与类似的查询相比,看起来第二行不是太慢),但我不知道如何修复它。我已经在连接中出现的每一列都有索引,但由于这些表是<derived2>等,我猜索引是无关紧要的。

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

编辑(回应Rick James):

MySQL版本是5.0.95(是的,我知道......)。虽然SHOW CREATE TABLE中的大部分字段都不相关,但这里涉及的三个表格personCREATE 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

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

尝试的建议:

我试图实现Rick James关于将子选项放在字段列表中的建议(我甚至不知道这是可能的),如下所示:

#1054 - Unknown column 'elimdate' in 'where clause'

但它抱怨: <template is="dom-repeat" items="{{response.0.sentence}}"> <div style="display: inline; postition: realative;"> <span>{{item.word}}></span> </div> </template> 根据文档,WHERE子句在字段列表之前被解释,所以这种方法不起作用。

1 个答案:

答案 0 :(得分: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,此建议可能不适用。