加入两个大表非常慢

时间:2015-11-11 18:24:32

标签: mysql left-join

我有两张大桌子要加入

第一个:

CREATE TABLE IF NOT EXISTS `cdr` (
  `calldate` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
  `clid` varchar(80) NOT NULL DEFAULT '',
  `src` varchar(80) NOT NULL DEFAULT '',
  `dst` varchar(80) NOT NULL DEFAULT '',
  `dcontext` varchar(80) NOT NULL DEFAULT '',
  `channel` varchar(80) NOT NULL DEFAULT '',
  `dstchannel` varchar(80) NOT NULL DEFAULT '',
  `lastapp` varchar(80) NOT NULL DEFAULT '',
  `lastdata` varchar(80) NOT NULL DEFAULT '',
  `duration` decimal(11,6) NOT NULL DEFAULT '0.000000',
  `billsec` decimal(11,6) NOT NULL DEFAULT '0.000000',
  `disposition` varchar(45) NOT NULL DEFAULT '',
  `amaflags` int(11) NOT NULL DEFAULT '0',
  `accountcode` varchar(20) NOT NULL DEFAULT '',
  `uniqueid` varchar(32) NOT NULL DEFAULT '',
  `userfield` varchar(255) NOT NULL DEFAULT '',
  `cost` char(20) NOT NULL DEFAULT 'none',
  `zone` char(60) NOT NULL DEFAULT 'none',
  `profile` char(10) NOT NULL DEFAULT 'none',
  `tariff` char(8) NOT NULL DEFAULT 'none',
  `status` char(10) NOT NULL DEFAULT 'none',
  `answer` datetime NOT NULL,
  `end` datetime NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=latin1;

--
-- Indexes for table `cdr`
--
ALTER TABLE `cdr`
 ADD KEY `src` (`src`), 
 ADD KEY `accountcode` (`accountcode`), 
 ADD KEY `status` (`status`), 
 ADD KEY `uniqueid` (`uniqueid`), 
 ADD KEY `calldate` (`calldate`);

第二个

CREATE TABLE IF NOT EXISTS `routes` (
  `id` int(4) NOT NULL,
  `route` char(35) NOT NULL,
  `zonenum` int(4) NOT NULL,
  `comment` char(50) CHARACTER SET latin2 DEFAULT NULL,
  `status` tinyint(1) NOT NULL DEFAULT '1',
  `wholesaledst` varchar(60) NOT NULL,
  `nabava` char(10) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=latin1;


ALTER TABLE `routes`
ADD PRIMARY KEY (`id`), ADD UNIQUE KEY `route` (`route`), ADD KEY `zonenum` (`zonenum`);

第一个表包含大约350万行,第二个表包含35000左右。对于第一个表中的每个记录,我必须从第二个表中获取zonenum。

这是我的查询

SELECT src, accountcode, zonenum, calldate, answer, end
FROM cdr  
LEFT OUTER JOIN routes ON src LIKE route
WHERE calldate BETWEEN '2015-03-01' AND '2015-04-01' AND status = 'INCOMING' AND accountcode != 110 AND disposition = 'ANSWERED';

例如 src看起来像095346435,路由看起来像095%

查询需要大约7分钟才能执行。如果我删除加入它只有1.5秒。 当检查mysql慢查询日志时,它说查询检查了140万行,这大约是routes表中where子句*行数之后的行数。我已经尝试过使用子查询,临时表......除了它总是太慢了 有什么方法可以加快查询速度吗?还是我错过了一些索引?请帮助我绝望。

更新

如果有帮助,这里有EXPLAIN结果

EXPLAIN

3 个答案:

答案 0 :(得分:2)

由于routes小得多,因此可能值得加入包含它的子查询。在该查询中,您可以SELECT LEFT(route, 3) AS rtPre, zonenum加入rtPre,如下所示:

SELECT cdr.src, cdr.accountcode, r.zonenum, cdr.calldate, cdr.answer, cdr.end
FROM cdr  
   LEFT JOIN (
      SELECT LEFT(route, 3) AS rtPre, zonenum
      FROM routes
   ) AS r ON LEFT(cdr.src, 3) = r.rtPre
WHERE cdr.calldate BETWEEN '2015-03-01' AND '2015-04-01' 
   AND cdr.status = 'INCOMING' 
   AND cdr.accountcode != 110 
   AND cdr.disposition = 'ANSWERED'
;

如果仍然没有帮助,您可以将该子查询插入到临时表中,索引位于rtPre;并在JOIN中使用该表。

如果要经常运行此类查询,您甚至可能需要考虑一个永久的"前缀" routes中可以编入索引的字段。

...当然,这是一个巨大的假设,即所有cdr.src值都是3个字符和%。 (如果这是一个错误的假设,前缀类型解决方案可能仍然可用。LEFT(cdr.src, [standard prefix length]) = r.rtPre AND r.route LIKE cdr.src可以利用最小前缀来减少所需的LIKE比较。

答案 1 :(得分:1)

您的查询未在路由表中使用任何键进行查找。 EXPLAIN中的索引意味着执行索引扫描,它仍然很糟糕,因为我们必须查看每条记录。

请注意,src LIKE route无法使用密钥。正如CindyH指出的那样,src = route会更好。当然它会给出不同的结果。你的逻辑中真的需要src LIKE route,还是足够好?

如果你确实需要LIKE,解决方案将取决于原因,但它将涉及在路由上构建某种形式的FULLTEXT索引,使用本机FULLTEXT或使用您自己的相关子串表的一些手动构造。或许还有其他一些创意解决方案

编辑:

根据附加信息,假设所有路线都只是前缀,我建议采用以下“创意”解决方案:

  • 编写一个存储的函数(或者只是一个很难看的生成的案例......何时)从src中提取前缀(国家代码?)并将%附加到它。
  • src LIKE route替换为concat(whatever computes the prefix, '%') = route

这将使用路线上的钥匙。

答案 2 :(得分:0)

了解如何构建最符合您要求的索引。您需要一个复合索引(有时也是COVERING索引),具体取决于它如何最适合您的表/情境。

由于您正在查看特定日期和状态的呼叫数据记录,我建议使用索引

cdr table index ( disposition, status, calldate, accountcode )

包含JOIN标准的覆盖索引也包括SRC列,例如......

cdr table index ( disposition, status, calldate, accountcode, src )

将索引视为优化排序的一种方法。如果您有两个房间,每个房间都有每个呼叫数据记录的副本。

会议室A仅按1列排序,例如帐户代码,您必须转到每组帐户,然后查找状态,区域,日期范围。

B室,按照上面的样本索引进行多重排序。所以,在房间里,假设有文件柜。每个柜子都有自己的配置,所以你直接跳到那些“答案”而忽略其他的。

在那个柜子里,有两个抽屉......进货和外出......所以现在你只有一个抽屉。

从抽屉中,按日期排序。所以现在您可以直接跳转到您想要合格记录的日期范围。那么这就是所有那些账户代码110 ......

的SKIPPING问题

这是如何思考索引如何工作的前提。永远不要只依赖单个列,您需要它们根据您的查询与其他列一起工作。

由于您需要从路线表中获取ZONE编号,因此应该具有COVERING索引

ROUTES表的索引......(路由,zonenum)

因此引擎无需返回原始数据页面即可获取区域。它是索引的一部分,可以直接返回。同样,如果您还需要第二部分,请不要依赖单个列作为索引。