我有两张大桌子要加入
第一个:
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
结果
答案 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 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)
因此引擎无需返回原始数据页面即可获取区域。它是索引的一部分,可以直接返回。同样,如果您还需要第二部分,请不要依赖单个列作为索引。