JOIN 慢查询

Posted

技术标签:

【中文标题】JOIN 慢查询【英文标题】:Slow query with JOIN 【发布时间】:2015-11-30 10:20:47 【问题描述】:

我有一个使用以下 JOIN 运行非常慢(6 秒)的存储过程:

JOIN tariffs t ON 
(LEFT(`cdrs`.`cnumber`,7) = t.numberrange 
OR LEFT(`cdrs`.`cnumber`,8) = t.numberrange)

如果没有上述 JOIN,查询将在 1.5 秒内运行。 有什么办法可以提高性能?完整的存储过程如下:

CREATE DEFINER=`xxx`@`%` PROCEDURE `GetHourStats`(IN _ID INT, IN _YEAR INT, IN _MONTH INT, IN _DAY INT)
BEGIN

set @_START = UNIX_TIMESTAMP(date(_YEAR * 10000 + _MONTH * 100 + _DAY * 1)); 
set @_END = UNIX_TIMESTAMP(date_add(date(_YEAR * 10000 + _MONTH * 100 + _DAY * 1), interval 1 day));

SELECT h.idhour, h.`hour` as 'hour', innumber, count(*) as `count`, sum(talktime) as `duration` FROM (
     SELECT 
        `cdrs`.`dcustomer` AS `dcustomer`,
        (CASE
            WHEN (LEFT(`cdrs`.`cnumber`, 2) = "01" OR LEFT(`cdrs`.`cnumber`, 2) = "02") THEN '01-02'
            WHEN (LEFT(`cdrs`.`cnumber`, 2) = "03") THEN '03'
            WHEN (LEFT(`cdrs`.`cnumber`, 2) = "05") THEN '05'
            WHEN (LEFT(`cdrs`.`cnumber`, 2) = "06") THEN '06'
            WHEN (LEFT(`cdrs`.`cnumber`, 2) = "07") THEN '07'
            WHEN (LEFT(`cdrs`.`cnumber`, 3) = "080") THEN '080'
            WHEN (LEFT(`cdrs`.`cnumber`, 3) = "084") THEN '084'
            WHEN (LEFT(`cdrs`.`cnumber`, 3) = "087") THEN '087'
            WHEN (LEFT(`cdrs`.`cnumber`, 2) = "09") THEN '09'
        END) AS 'innumber',
        FROM_UNIXTIME(`cdrs`.`start`) AS `start`,
         (`cdrs`.`end` - `cdrs`.`start`) AS `duration`,
         `cdrs`.`cnumber` AS `calling`,
         `cdrs`.`talktime` AS `talktime`
    FROM `cdrs`
    JOIN tariffs t ON (LEFT(`cdrs`.`cnumber`,7) = t.numberrange OR LEFT(`cdrs`.`cnumber`,8) = t.numberrange)
    WHERE `cdrs`.`start` >= @_START and `cdrs`.`start` < @_END
    AND `cdrs`.`stype` = _LATIN1'external'
    AND `cdrs`.`talktime` >= 5 
    AND `cdrs`.`status` = 'answer'
    AND CHAR_LENGTH(`cdrs`.`cnumber`) = 11
    GROUP BY callid
   ) cdr 

   JOIN customers c ON c.id = cdr.dcustomer
   LEFT JOIN hub.hours h ON HOUR(cdr.`start`) = h.idhour

    WHERE (c.parent = _ID or cdr.dcustomer = _ID or c.parent IN 
        (SELECT id FROM customers WHERE parent = _ID))

   GROUP BY h.idhour, cdr.innumber
   ORDER BY h.idhour;

END

问:如何让上述存储过程运行得更快?

【问题讨论】:

您可以添加一个仅存储前两位或三位数字的索引列。 显然你的桌子设计不好。 cnumber 不仅仅是一个数字,它的各个部分都有意义。这使得查询数据变慢。重新设计您的表,以便单独存储单独的数据并且查询会更快(当然提供了适当的索引)。 为什么不把JOIN tariffs t ON改成LEFT JOIN tariffs t ON?我想你会得到更好的表现 【参考方案1】:
JOIN tariffs t ON 
        (LEFT(`cdrs`.`cnumber`,7) = t.numberrange 
      OR LEFT(`cdrs`.`cnumber`,8) = t.numberrange)

这可能会给你相同的设置:

JOIN tariffs t 
      ON cdrs.cnumber LIKE CONCAT(t.numberrange, '%')

如果 numberrange 的长度不是 7 或 8 个字符,它会失败,但也许“不能”发生。也许在某处添加它会解决这个问题:

 AND LENGTH(t.numberrange) BETWEEN 7 AND 8

 INDEX(numberrange) -- no longer useful
 INDEX(cnumber) -- may be useful here.

【讨论】:

这改善了响应,但幅度不大,不到 1 秒。在cnumber 上添加索引似乎确实有帮助,但在简单查询中,而在连接上未使用该索引。关于如何在上述连接中使用索引的任何想法?如果需要,我会发布解释语句的结果。 "关于简单查询" -- 还有其他查询吗?如果不考虑所有查询,就无法构建最佳 INDEX。

以上是关于JOIN 慢查询的主要内容,如果未能解决你的问题,请参考以下文章

在此查询上使用 RIGHT JOIN 时 JOIN 非常慢

使用 ORDERBY 时的 MySQL 慢 JOIN 查询

使用 VIEW 和 LEFT OUTER JOIN 进行慢查询

什么会导致连接比划分为两个查询慢?

使用 LEFT JOIN 和 ORDER BY...LIMIT 查询慢,使用 Filesort

即使使用 INNER JOIN 而不是 IN,MySQL 查询也非常慢