基本上对查询存在一些性能问题,主要是我保存呼叫数据的最大表。
主查询包含很多左连接和&子选择,但在我正在运行查询的情况下,我希望返回1.3M调用,查询只是没有这样做。不得不在7分钟停止它意味着某处肯定存在问题。
我缩小了主要查询并测试了最简单的子选择连接
SELECT
DateStart,
ID,
NumbID,
EffectiveFlag,
OrigNumber
FROM calls
WHERE
DateStart <= '2013-12-31'
AND DateStart >= '2013-01-01'
AND CallLength >= '00:00:00'
AND Direction = '1'
AND CustID IN (474,482,250,268,197,604,132,359,279,441,118,448,152,133,380,162,249,679,226,259,2450,2408,2451,2453,2439,2454,2444,2445,2452)
即使该查询需要4.5秒 - 所以当它是带有其他连接的查询中的子选择时。亚选,我可以想象为什么整个查询都无法使用。
上述查询的解释声明是
+----+-------------+-------+-------+-------------------------------------------------------------------------------------------------------+----------------------+---------+------+---------+-------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+-------+-------------------------------------------------------------------------------------------------------+----------------------+---------+------+---------+-------------+
| 1 | SIMPLE | calls | range | idx_CustID,idx_DateStart,idx_CustID_DateStart,idx_CustID_TermNumber,idx_Direction | idx_CustID_DateStart | 7 | NULL | 1660009 | Using where |
+----+-------------+-------+-------+-------------------------------------------------------------------------------------------------------+----------------------+---------+------+---------+-------------+
调用表的数据库模式是
+-------------------+-------------+------+-----+---------------------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-------------------+-------------+------+-----+---------------------+----------------+
| ID | int(11) | NO | PRI | NULL | auto_increment |
| CustID | int(11) | NO | MUL | 0 | |
| CarrID | int(11) | NO | MUL | NULL | |
| TariID | int(11) | NO | MUL | 0 | |
| CarrierRef | varchar(30) | NO | MUL | | |
| NumbID | int(11) | NO | MUL | 0 | |
| VlviID | int(11) | NO | MUL | NULL | |
| VcamID | int(11) | NO | MUL | NULL | |
| SomeID | int(11) | NO | MUL | NULL | |
| VlnsID | int(11) | NO | MUL | NULL | |
| NGNumber | varchar(12) | NO | | | |
| OrigNumber | varchar(16) | NO | MUL | NULL | |
| CLIRestrictedFlag | int(2) | NO | | NULL | |
| OrigLocality | varchar(11) | NO | MUL | | |
| OrigAreaCode | varchar(11) | NO | MUL | | |
| TermNumber | varchar(16) | NO | MUL | NULL | |
| BatchNumber | varchar(10) | NO | MUL | | |
| DateStart | date | NO | MUL | 0000-00-00 | |
| DateClear | date | NO | | 0000-00-00 | |
| TimeStart | time | NO | | 00:00:00 | |
| TimeClear | time | NO | | 00:00:00 | |
| CallLength | time | NO | | 00:00:00 | |
| RingLength | time | NO | | 00:00:00 | |
| EffectiveFlag | smallint(1) | NO | MUL | NULL | |
| UnansweredFlag | smallint(1) | NO | MUL | NULL | |
| EngagedFlag | smallint(1) | NO | | NULL | |
| RecID | int(11) | NO | MUL | NULL | |
| CreatedUserID | int(11) | NO | | 0 | |
| CreatedDatetime | datetime | NO | MUL | 0000-00-00 00:00:00 | |
| Direction | int(1) | NO | MUL | NULL | |
+-------------------+-------------+------+-----+---------------------+----------------+
调用表上的索引是
+-------+------------+---------------------------+--------------+-----------------+-----------+-------------+----------+--------+------+------------+---------+
| Table | Non_unique | Key_name | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment |
+-------+------------+---------------------------+--------------+-----------------+-----------+-------------+----------+--------+------+------------+---------+
| calls | 0 | PRIMARY | 1 | ID | A | 23905312 | NULL | NULL | | BTREE | |
| calls | 1 | idx_CustID | 1 | CustID | A | 1685 | NULL | NULL | | BTREE | |
| calls | 1 | idx_NumbID | 1 | NumbID | A | 37765 | NULL | NULL | | BTREE | |
| calls | 1 | idx_OrigNumber | 1 | OrigNumber | A | 5976328 | NULL | NULL | | BTREE | |
| calls | 1 | idx_OrigLocality | 1 | OrigLocality | A | 45019 | NULL | NULL | | BTREE | |
| calls | 1 | idx_OrigAreaCode | 1 | OrigAreaCode | A | 846 | NULL | NULL | | BTREE | |
| calls | 1 | idx_TermNumber | 1 | TermNumber | A | 232090 | NULL | NULL | | BTREE | |
| calls | 1 | idx_DateStart | 1 | DateStart | A | 4596 | NULL | NULL | | BTREE | |
| calls | 1 | idx_EffectiveFlag | 1 | EffectiveFlag | A | 2 | NULL | NULL | | BTREE | |
| calls | 1 | idx_UnansweredFlag | 1 | UnansweredFlag | A | 2 | NULL | NULL | | BTREE | |
| calls | 1 | idx_EngagedFlag | 1 | UnansweredFlag | A | 2 | NULL | NULL | | BTREE | |
| calls | 1 | idx_TariID | 1 | TariID | A | 110 | NULL | NULL | | BTREE | |
| calls | 1 | idx_CustID_DateStart | 1 | CustID | A | 1685 | NULL | NULL | | BTREE | |
| calls | 1 | idx_CustID_DateStart | 2 | DateStart | A | 919435 | NULL | NULL | | BTREE | |
| calls | 1 | idx_NumbID_DateStart | 1 | NumbID | A | 37765 | NULL | NULL | | BTREE | |
| calls | 1 | idx_NumbID_DateStart | 2 | DateStart | A | 5976328 | NULL | NULL | | BTREE | |
| calls | 1 | idx_RecID | 1 | RecID | A | 288015 | NULL | NULL | | BTREE | |
| calls | 1 | idx_CarrierRef | 1 | CarrierRef | A | 7968437 | NULL | NULL | | BTREE | |
| calls | 1 | idx_CustID_CallTermNumber | 1 | CustID | A | 1685 | NULL | NULL | | BTREE | |
| calls | 1 | idx_CustID_CallTermNumber | 2 | TermNumber | A | 246446 | NULL | NULL | | BTREE | |
| calls | 1 | idx_CreatedDatetime | 1 | CreatedDatetime | A | 771139 | NULL | NULL | | BTREE | |
| calls | 1 | idx_Direction | 1 | Direction | A | 2 | NULL | NULL | | BTREE | |
| calls | 1 | idx_VlviID | 1 | VlviID | A | 50539 | NULL | NULL | | BTREE | |
| calls | 1 | idx_SomeID | 1 | SomeID | A | 30 | NULL | NULL | | BTREE | |
| calls | 1 | idx_VcamID | 1 | VcamID | A | 64 | NULL | NULL | | BTREE | |
| calls | 1 | idx_VlnsID | 1 | VlnsID | A | 191 | NULL | NULL | | BTREE | |
| calls | 1 | idx_CarrID | 1 | CarrID | A | 4 | NULL | NULL | | BTREE | |
| calls | 1 | idx_BatchNumber | 1 | BatchNumber | A | 271651 | NULL | NULL | | BTREE | |
+-------+------------+---------------------------+--------------+-----------------+-----------+-------------+----------+--------+------+------------+---------+
我理解的东西可能导致性能,是具有低基数的列上的索引。我知道诸如Direction之类的基数为2的基数实际上可能在性能方面更糟糕,但仅此一点不应该使语句变得如此缓慢。
就具有有价值索引的基数要求而言,与总表记录相比,是否存在一般基数百分比,其中索引会提高性能以及何时降低性能?
据我所知,没有人能够向我发出一个将查询时间从4.5秒更改为0.01秒的答案,但对查询本身,表架构,索引或者硬件将不胜感激。
更新
@Sebas“请你重新运行查询并解释没有零件的计划:AND CallLength&gt; = '00:00:00'AND Direction ='1'please?”
+----+-------------+-------+-------+---------------------------------------------------------------------+----------------------+---------+------+--------+-------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+-------+---------------------------------------------------------------------+----------------------+---------+------+--------+-------------+
| 1 | SIMPLE | calls | range | idx_CustID,idx_DateStart,idx_CustID_DateStart,idx_CustID_TermNumber | idx_CustID_DateStart | 7 | NULL | 724813 | Using where |
+----+-------------+-------+-------+---------------------------------------------------------------------+----------------------+---------+------+--------+-------------+
答案 0 :(得分:4)
您的“DateStart”是截断日期时间 - 仅保留日期吗?如果没有,您可能希望构建一个具有截断值(按天或小时),并使用int数据类型,这将使索引更小,以便更快地进行查询。
或者,另一种优化方式(黄金法则#1不做,#2现在不做)。
当且仅当您的日期和PK按顺序同步时,您可以构建范围为StartDate&lt; =&gt;的外部索引。 ID(PK)。
并使用以下模式
SELECT @start:=ID_START FROM ANOTHER_TABLE WHERE StartDate='2013-01-01'
SELECT @end:=ID_END FROM ANOTHER_TABLE WHERE StartDate='2013-12-31'
SELECT * FROM calls WHERE ID BETWEEN @start and @end AND CustId in (xxxxx) ....
通过使用上述模式,Mysql将知道是否只扫描一段表。
答案 1 :(得分:3)
就像Darhazer所说,你有太多的索引,首先要删除所有索引并根据你的需要再次构建它们。
对于此特定查询,请在其中创建一个 INDEX,其中包含以下字段:
DateStart
CallLength
Direction
CustID
将AND Direction = '1'
更改为AND Direction = 1
(删除引号,您要比较整数,而不是字符串)
看看这对您的查询时间有何影响。如果顺利,请添加子查询,使用EXPLAIN再次检查,添加所需的索引等。
答案 2 :(得分:3)
您的查询应该点击的最佳索引是idx_CustID_DateStart
。 IN
语句阻止了这种情况的发生。如果CustID
列表来自表格,我建议将JOIN
列入,而不是枚举。
答案 3 :(得分:2)
我不确定当你担心需要5秒的子查询(希望每行不执行)时,原始查询需要超过7分钟。但无论如何,如果你想加快这个速度,你应该阅读索引的工作原理。我建议先this article开始。
基本上你有4个字段的条件,而在两个字段上这些是范围条件。如果您已阅读该文章,则您知道在满足第一个范围条件之前,索引将被有效使用。但是,索引中的其余数据可用于索引扫描。因此,您需要选择哪种条件可以更好地缩小结果集:DateStart
或CallLength
。
无论如何,您需要一个以(CustID, Direction ...
开头的复合索引。我的感觉是DateStart上的条件更好。因此,我将从(CustID, Direction, DateStart, CallLength)
开始,并将其与(CustID, Direction, DateStart)
进行比较,因为最后一个字段可能无法提供足够的性能增益,但会占用内存资源。
虽然我仍然认为,在专注于子查询时,应该确保查询的其余部分是正确编写的。可能有一种合理的方式来组织查询,因此这种优化会变得无关紧要。
答案 4 :(得分:2)
这160万的总数百分之几是多少?如果它们用于返回数据集的最小部分,则索引很好,但是由于它们的data access pattern with mrr is random reads,有时在表上使用fullscan会更有效。当然,这取决于如何将数据添加到表中以及如何在磁盘上分配空间。
此外,您可能会发现使用MySQL performance schema监控效果非常有用,请查看here了解详细信息。
答案 5 :(得分:1)
索引太多了。例如,您不需要单独的CustID索引,因为它是CustID中最左侧的DateStart。 UnansweredFlag上有2个索引。你真的需要所有这些索引吗?这不仅减慢了插入/更新速度,还降低了优化器的速度,并且可能欺骗优化器选择不那么好的索引。
现在,关于具体查询。您需要查看哪个字段或组合限制查询最多(现在它扫描1,6M行!)并强制它使用该索引。因此,对于指定了DateStart的每个where子句(方向,调用长度)运行SELECT COUNT(*)查询(您总是希望基于此限制)。也许你只需要为索引添加方向。
此外,在MySQL 5.6之前,WHERE子句中的子查询未进行优化,因此您可能应该重写整个查询以使用join而不是subselect,而不是优化特定查询