为什么这个select语句这么慢?

时间:2018-05-13 23:06:21

标签: mysql query-performance

这个select语句运行得非常慢。完成执行需要10秒以上。可能会更长,但我无法知道,因为与MySQL的连接超时。这是一个单独的问题。

以下是代码:

SELECT 
    f.id, f.name, GROUP_CONCAT(DISTINCT (c.firstname)) children
FROM
    families f,
    children c,
    transactions t
WHERE
    f.companyid = 1170 AND f.id = t.familyid
        AND f.id = c.familyid
        AND t.transactiontype = 'P'
        AND t.taxdeductible = 'Y'
        AND YEAR(t.date) = 2017
        AND status = 'A'
        OR f.id = 9779432
GROUP BY f.id
ORDER BY name;

我确实有来自families.companyid,children.familyid,transactions.transactiontype,transactions.taxdeductible和transactions.date的索引。

有没有理由为什么它会进行全表扫描,尽管我的索引?或者是否有其他原因导致此查询运行缓慢?

编辑:根据以下评论填写一些空白:

  • 子表包含73,000行中的17MB数据。
  • 系列表在56,000行中有6MB的数据
  • 交易表在980,000行中有83MB的数据。

    儿童表

    CREATE TABLE `children` (
      `id` int(11) NOT NULL AUTO_INCREMENT,
      `familyid` int(10) unsigned DEFAULT '0',
      `companyid` int(11) DEFAULT '0',
      `picture` varchar(250) DEFAULT NULL,
      `stockpicture` varchar(1) DEFAULT 'N',
      `firstname` varchar(250) DEFAULT NULL,
      `lastname` varchar(250) DEFAULT NULL,
      `nickname` varbinary(250) DEFAULT NULL,
      `birthdate` date NOT NULL DEFAULT '0000-00-00',
      `usecustomfee` varchar(1) NOT NULL DEFAULT 'N',
      `usecustomproviderfee` varchar(1) NOT NULL DEFAULT 'N',
      `customfee` decimal(10,2) DEFAULT '0.00',
      `customfeetypecode` varchar(45) DEFAULT 'MONTH',
      `customproviderfee` decimal(10,2) DEFAULT '0.00',
      `customproviderfeetypecode` varchar(45) DEFAULT 'MONTH',
      `usecustomchargeitem` varchar(1) DEFAULT 'N',
      `customchargeitem` int(11) DEFAULT '0',
      `dailyrate` decimal(10,2) DEFAULT '55.00',
      `startdate` date DEFAULT NULL,
      `enddate` date DEFAULT NULL,
      `subsidynotrequired` char(1) NOT NULL DEFAULT 'Y',
      `subsidychildid` varchar(250) DEFAULT NULL,
      `subsidyapplicantid` varchar(250) DEFAULT NULL,
      `subsidynote` text,
      `waitingsince` date DEFAULT NULL,
      `waitingroom` int(11) DEFAULT NULL,
      `waitingtype` varchar(1) DEFAULT 'F',
      `preferredstart` date DEFAULT NULL,
      `registrationdate` date DEFAULT NULL,
      `groupid` int(11) NOT NULL DEFAULT '0',
      `providerisparent` varchar(1) NOT NULL DEFAULT 'N',
      `attendingschool` char(1) NOT NULL DEFAULT 'N',
      `schoolname` varchar(250) DEFAULT NULL,
      `liveswithmother` char(1) NOT NULL DEFAULT 'Y',
      `liveswithfather` char(1) NOT NULL DEFAULT 'Y',
      `liveswithother` char(1) NOT NULL DEFAULT 'N',
      `otherguardian` varchar(250) DEFAULT NULL,
      `sex` char(1) NOT NULL DEFAULT 'M',
      `note` text,
      `archived` char(1) NOT NULL DEFAULT 'N',
      `priorityid` int(11) DEFAULT '0',
      `onlineregistration` varchar(1) NOT NULL DEFAULT 'N',
      `onlineregistrationaccept` varchar(1) NOT NULL DEFAULT 'N',
      `registrationconfirmed` varchar(1) NOT NULL DEFAULT 'N',
      `registrationconfirmeddate` datetime DEFAULT NULL,
      `createddate` datetime DEFAULT NULL,
      `modifieddate` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
      `fullpart` varchar(1) DEFAULT 'F',
      `parttimedays` int(11) DEFAULT '10',
      `parttimedaystype` varchar(45) DEFAULT 'D',
      `parttimedaystypecode` varchar(45) DEFAULT 'MONTH',
      `program` varchar(45) DEFAULT 'daycare',
      `registrationnote` varchar(2000) DEFAULT NULL,
      `registrationnoteread` varchar(1) DEFAULT 'N',
      `registrationsubsidy` varchar(45) DEFAULT 'noplan',
      `registrationsubsidydate` datetime DEFAULT NULL,
      `registrationsubsidyamount` decimal(10,2) DEFAULT '0.00',
      PRIMARY KEY (`id`),
      KEY `Familyid` (`familyid`),
      KEY `companyid` (`companyid`),
      KEY `startdate` (`startdate`),
      KEY `enddate` (`enddate`),
      KEY `roomid` (`groupid`),
      KEY `providerisparent` (`providerisparent`)
    ) ENGINE=InnoDB AUTO_INCREMENT=93685 DEFAULT CHARSET=latin1;
    

    家庭表

    CREATE TABLE `families` (
      `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
      `accountnumber` varchar(100) DEFAULT NULL,
      `name` varchar(245) NOT NULL COMMENT 'The account name will typically be the name of the parent responsible for payment',
      `motherid` int(10) unsigned NOT NULL,
      `fatherid` int(10) unsigned NOT NULL,
      `balance` decimal(10,2) NOT NULL DEFAULT '0.00',
      `notes` varchar(2000) DEFAULT NULL,
      `companyid` int(10) unsigned NOT NULL,
      `status` varchar(1) NOT NULL DEFAULT 'A',
      `financialaidrequired` char(1) NOT NULL DEFAULT 'N',
      `intakesurveyid` int(10) unsigned DEFAULT NULL,
      `referralid` int(10) unsigned NOT NULL DEFAULT '0',
      `registrationemailrequired` varchar(1) DEFAULT 'N',
      `registrationemailsent` varchar(1) DEFAULT 'N',
      `registrationemaildate` date DEFAULT NULL,
      `registrationemailaddressfound` varchar(1) DEFAULT NULL,
      `waitinglistemailrequired` varchar(1) DEFAULT 'N',
      `waitinglistemailsent` varchar(1) DEFAULT 'N',
      `waitinglistemaildate` date DEFAULT NULL,
      `waitinglistemailaddressfound` varchar(1) DEFAULT NULL,
      `activationemailrequired` varchar(1) DEFAULT 'N',
      `activationemailsent` varchar(1) DEFAULT 'N',
      `activationemaildate` date DEFAULT NULL,
      `activationemailaddressfound` varchar(1) DEFAULT NULL,
      `createddate` datetime DEFAULT NULL,
      PRIMARY KEY (`id`),
      KEY `companyid` (`companyid`),
      KEY `intakesurveyid` (`intakesurveyid`),
      KEY `status` (`status`)
    ) ENGINE=InnoDB AUTO_INCREMENT=9803007 DEFAULT CHARSET=latin1;
    

    交易表

    CREATE TABLE `transactions` (
      `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
      `familyid` int(10) unsigned NOT NULL,
      `date` datetime NOT NULL,
      `transactiontype` varchar(1) NOT NULL DEFAULT 'C' COMMENT '''C'' = Charge, ''P'' = Payment',
      `paymenttype` varchar(3) DEFAULT NULL COMMENT '''DBT'' = Debit, ''CSH'' = Cash, ''CRE'' = Credit Card, ''CHQ'' = Cheque, ''MNY'' = Money Order,''EFT'' = Electronic Funds Transfer',
      `comment` varchar(500) DEFAULT NULL,
      `amount` decimal(10,2) NOT NULL DEFAULT '0.00',
      `reference` varchar(45) DEFAULT NULL,
      `chargeitem` int(10) unsigned DEFAULT '0',
      `taxdeductible` varchar(1) NOT NULL DEFAULT 'Y',
      `payer` varchar(1) DEFAULT 'M',
      `createddate` datetime DEFAULT NULL,
      `modifieddate` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
      PRIMARY KEY (`id`),
      KEY `Familyid` (`familyid`),
      KEY `Transaction Type` (`transactiontype`),
      KEY `Tax Deductible` (`taxdeductible`),
      KEY `date` (`date`)
    ) ENGINE=InnoDB AUTO_INCREMENT=1013472 DEFAULT CHARSET=latin1 ROW_FORMAT=DYNAMIC;
    
  • 4 个答案:

    答案 0 :(得分:0)

    尝试

    EXPLAIN
    SELECT 
        f.id, f.name, GROUP_CONCAT(DISTINCT (c.firstname)) children
    FROM
        families f,
        children c,
        transactions t
    WHERE
        f.companyid = 1170 AND f.id = t.familyid
            AND f.id = c.familyid
            AND t.transactiontype = 'P'
            AND t.taxdeductible = 'Y'
            AND YEAR(t.date) = 2017
            AND f.status = 'A'
            OR f.id = 9779432
    GROUP BY f.id
    ORDER BY name;
    

    确保加载了正确的索引

    你说你“有索引”,但每个查询只能使用1个索引,为你需要的查询设置1索引。

    另外我建议永远不要使用倍数from,但是使用JOIN语句而不是能够针对连接的表索引和索引进行目标

    答案 1 :(得分:0)

    请提供您的表架构。我们需要检查你有哪些索引。

    同时,您可以尝试JOIN个表并删除ORDER BY。 从我看到你只有一个f.id = 9779432,你为什么要订购相同的价值?

    检查OR条件,我已将其转化为对我有意义的事情。您对该广泛OR的初始陈述意味着您需要任何YEAR(t.date) OR f.id = 9779432这对您有意义吗?

    SELECT 
        f.id, f.name, GROUP_CONCAT(DISTINCT (c.firstname)) children
    FROM
        families f
    INNER JOIN children c
    ON f.id = c.familyid
    INNER JOIN transactions t
    ON f.id = t.familyid
       AND t.transactiontype = 'P'
       AND t.taxdeductible = 'Y'
       AND YEAR(t.date) = 2017
    WHERE
        (f.companyid = 1170 OR f.id = 9779432)
        AND f.status = 'A'
    
    GROUP BY f.id;
    

    答案 2 :(得分:0)

    最好使用21世纪的JOIN语法。

    SELECT f.id, f.name, GROUP_CONCAT(DISTINCT (c.firstname)) children
      FROM families f
      JOIN children c ON f.id = c.familyid
      JOIN transactions t ON f.id = t.familyid
     WHERE f.companyid = 1170 
       AND t.transactiontype = 'P'
       AND t.taxdeductible = 'Y'
       AND YEAR(t.date) = 2017
       AND status = 'A'
        OR f.id = 9779432
     GROUP BY f.id
     ORDER BY name;
    

    AND YEAR(t.date) = 2017更改为AND t.date >='2017-01-01 AND t.date < '2018-01-01'。为什么?该过滤条款的YEAR()形式不是sargeable

    您的问题无法确定哪个表包含status列,并且对于性能而言非常重要 。如果是t.status,请尝试在

    上创建复合索引
     transaction(status, transactiontype, taxdeductible, date, familyid)
    

    然后在

    上尝试复合索引
     transaction(familyid, status, transactiontype, taxdeductible, date)
    

    其中一个应该有很多帮助。为什么?当在transaction表上满足您的查询时,MySQL可以随机访问索引到第一个符合条件的记录:匹配所有=条件并具有最低值date的记录。然后它可以按顺序扫描索引,直到找到最后一个符合条件的日期。

    使用效果最佳的索引。

    如果status表中的transaction列不在该表中,请将其从该索引中取出。

    答案 3 :(得分:0)

    假设你的意思是这(MySQL将如何解释它):

    (this AND that ...) OR (f.id=...)
    

    我们使用UNION代替OR。 (OR优化得很差。)

    我们也使用'标准'JOIN...ON代替'commajoin'。

    我们不要隐藏函数中的列(YEAR);它禁止使用索引。

    您已经因为没有说哪个表包含status而受到谴责。我看到Hamoon意外地丢失了statusf中的事实(?)。我会假设。

    DISTINCT不是函数,所以我删除了它后面的parens。

    我会选择UNION DISTINCT(较慢,但匹配OR的语义)而不是UNION ALL(更快,但可能会重复一行)。

    我会将children移到外部SELECT以避免一些潜在的打嗝。

    GROUP BYORDER BY匹配时,查询可以更快地运行。因此,假设idname在逻辑上捆绑在一起,我认为这将为您提供相同的分组和排序:

    GROUP BY name, id
    ORDER BY name, id
    

    将我的所有提示放在一起:

    SELECT  x.id, x.name,
            GROUP_CONCAT(DISTINCT c.firstname) children
        FROM (
               ( SELECT  f.id, f.name,
                    FROM  families f
                    JOIN  transactions t  ON f.id = t.familyid
                    WHERE  f.companyid = 1170
                      AND  t.transactiontype = 'P'
                      AND  t.taxdeductible = 'Y'
                      AND  t.date >= '2017-01-01'
                      AND  t.date <  '2017-01-01' + INTERVAL 1 YEAR
                      AND  f.status = 'A'
               )
               UNION DISTINCT
               ( SELECT   f.id, f.name
                    FROM  families f
                    WHERE  f.id = 9779432
               ) 
             ) AS x
        JOIN  children c  ON x.id = c.familyid
        GROUP BY  x.name, x.id
        ORDER BY  x.name, x.id 
    

    您将需要这些索引。列排序通常很重要。

    f:  I assume it has PRIMARY KEY(id)
    f:  (companyid, status)   -- in either order
    t:  (familyid, transactiontype, taxdeductible, date)
    t:  (transactiontype, taxdeductible, date, familyid)
    c:  (familyid, firstname)
    

    一些注意事项:

    • 我为t提供了2个索引 - 同时提供这两个索引,从而让优化程序决定是从f还是t开始。
    • 有些索引是“覆盖”的,因此可以提供额外的提升。
    • 重新制定后,DISTINCT中的GROUP_CONCAT可能是不必要的。
    • 多个单列索引通常与“复合”(多列)索引一样有用。