如何优化此mysql查询以找到最大的同时调用数?
我正在计算最大同时呼叫数。我的查询(我相信是准确的)太长了,因为有250000行。cdrs表如下所示:如何优化此mysql查询以找到最大的同时调用数?,mysql,Mysql,我正在计算最大同时呼叫数。我的查询(我相信是准确的)太长了,因为有250000行。cdrs表如下所示: +---------------+-----------------------+------+-----+---------+----------------+ | Field | Type | Null | Key | Default | Extra | +---------------+-----------------
+---------------+-----------------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+---------------+-----------------------+------+-----+---------+----------------+
| id | bigint(20) unsigned | NO | PRI | NULL | auto_increment |
| CallType | varchar(32) | NO | | NULL | |
| StartTime | datetime | NO | MUL | NULL | |
| StopTime | datetime | NO | | NULL | |
| CallDuration | float(10,5) | NO | | NULL | |
| BillDuration | mediumint(8) unsigned | NO | | NULL | |
| CallMinimum | tinyint(3) unsigned | NO | | NULL | |
| CallIncrement | tinyint(3) unsigned | NO | | NULL | |
| BasePrice | float(12,9) | NO | | NULL | |
| CallPrice | float(12,9) | NO | | NULL | |
| TransactionId | varchar(20) | NO | | NULL | |
| CustomerIP | varchar(15) | NO | | NULL | |
| ANI | varchar(20) | NO | | NULL | |
| ANIState | varchar(10) | NO | | NULL | |
| DNIS | varchar(20) | NO | | NULL | |
| LRN | varchar(20) | NO | | NULL | |
| DNISState | varchar(10) | NO | | NULL | |
| DNISLATA | varchar(10) | NO | | NULL | |
| DNISOCN | varchar(10) | NO | | NULL | |
| OrigTier | varchar(10) | NO | | NULL | |
| TermRateDeck | varchar(20) | NO | | NULL | |
+---------------+-----------------------+------+-----+---------+----------------+
我有以下索引:
+-------+------------+-----------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
| Table | Non_unique | Key_name | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment | Index_comment |
+-------+------------+-----------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
| cdrs | 0 | PRIMARY | 1 | id | A | 269622 | NULL | NULL | | BTREE | | |
| cdrs | 1 | id | 1 | id | A | 269622 | NULL | NULL | | BTREE | | |
| cdrs | 1 | call_time_index | 1 | StartTime | A | 269622 | NULL | NULL | | BTREE | | |
| cdrs | 1 | call_time_index | 2 | StopTime | A | 269622 | NULL | NULL | | BTREE | | |
+-------+------------+-----------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
我正在运行的查询是:
SELECT MAX(cnt) AS max_channels FROM
(SELECT cl1.StartTime, COUNT(*) AS cnt
FROM cdrs cl1
INNER JOIN cdrs cl2
ON cl1.StartTime
BETWEEN cl2.StartTime AND cl2.StopTime
GROUP BY cl1.id)
AS counts;
似乎我每天都必须将这些数据分块,并将结果存储在一个单独的表中,如
同步调用内联视图并非绝对必要。(使用内联视图在查询上运行解释的时间是正确的,解释将具体化内联视图(即运行内联视图查询并填充派生表),然后对外部查询进行解释
请注意,此查询将返回等效的结果:
SELECT COUNT(*) AS max_channels
FROM cdrs cl1
JOIN cdrs cl2
ON cl1.StartTime BETWEEN cl2.StartTime AND cl2.StopTime
GROUP BY cl1.id
ORDER BY max_channels DESC
LIMIT 1
尽管它仍然需要完成所有的工作,而且可能没有更好的表现;解释应该运行得更快(我们希望在额外的专栏中看到“使用临时文件;使用文件排序”)
结果集中的行数将是表中的行数(~250000行),这些行需要排序,所以这将是一段时间。更大的问题(我的直觉告诉我)是连接操作
我想知道如果在谓词中交换cl1和cl2,解释(或性能)是否会有所不同,即
ON cl2.StartTime BETWEEN cl1.StartTime AND cl1.StopTime
我在想,只是因为我想尝试一个相关的子查询。这大约是250000次执行,而且不可能再快了
SELECT ( SELECT COUNT(*)
FROM cdrs cl2
WHERE cl2.StartTime BETWEEN cl1.StartTime AND cl1.StopTime
) AS max_channels
, cl1.StartTime
FROM cdrs cl1
ORDER BY max_channels DESC
LIMIT 11
您可以对此进行解释,我们仍然会看到“使用临时;使用文件排序”,它还将显示“依赖子查询”
显然,在cl1表上添加一个谓词可以减少要返回的行数(例如,只检查过去15天);这应该会加快速度,但这并不能得到您想要的答案
WHERE cl1.StartTime > NOW() - INTERVAL 15 DAY
(我在这里的任何思考都不是对您的问题的肯定回答,也不是对性能问题的解决方案;它们只是思考。)我确信您不仅想知道最大同时呼叫数,而且想知道何时发生
我将创建一个包含每分钟时间戳的表
CREATE TABLE times (ts DATETIME UNSIGNED AUTO_INCREMENT PRIMARY KEY);
INSERT INTO times (ts) VALUES ('2014-05-14 00:00:00');
. . . until 1440 rows, one for each minute . . .
然后把它和电话联系起来
SELECT ts, COUNT(*) AS count FROM times
JOIN cdrs ON times.ts BETWEEN cdrs.starttime AND cdrs.stoptime
GROUP BY ts ORDER BY count DESC LIMIT 1;
以下是我的测试结果(在MacBookPro上运行的Linux虚拟机上的MySQL 5.6.17):
这实现了几个目标:
- 将检查的行数减少两个数量级
- 将执行时间从3小时以上减少到1分钟左右
- 还返回找到最高计数时的实际时间戳
以下是对我的问题的解释:
explain select ts, count(*) from times join cdrs on times.ts between cdrs.starttime and cdrs.stoptime group by ts order by count(*) desc limit 1;
+----+-------------+-------+-------+---------------+---------+---------+------+--------+------------------------------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+-------+---------------+---------+---------+------+--------+------------------------------------------------+
| 1 | SIMPLE | times | index | PRIMARY | PRIMARY | 5 | NULL | 1440 | Using index; Using temporary; Using filesort |
| 1 | SIMPLE | cdrs | ALL | starttime | NULL | NULL | NULL | 260727 | Range checked for each record (index map: 0x4) |
+----+-------------+-------+-------+---------------+---------+---------+------+--------+------------------------------------------------+
请注意行列中的数字,并将其与原始查询的解释进行比较。您可以通过将这些数字相乘来估计检查的行总数(但如果您的查询不是简单的,则会变得更复杂).需要多长时间?您希望需要多长时间?另外,您可以向我们展示查询的解释吗?我认为这无关紧要,但您不需要在子查询中选择cl1.StartTime
。您只需要COUNT(*)
@AndyLester现在三个小时就要开始了哈哈。我本来打算做一个解释,但不想再让它慢下来。不需要解释,它会告诉你这是在进行625亿次比较,很可能还将临时表存储在磁盘上。我想你需要找到另一种方法,甚至不是SQL,来找到这个t韩在一张大桌子上进行了自我连接。@Jason,你可能比我更了解钱。尽管如此,我还是觉得选择浮点而不是十进制是非常奇怪的!非常感谢你的思考;)我在你的第二次查询中出现了以下错误:where子句中的未知列“cl1.EndTime”
@Jason:完全是我的错。您查询中的列名是StopTime
,我在回答中的每一处都错误地键入了EndTime
。我会修改我的答案,使列名与你的列名匹配。哈,我应该抓住这个。下面是@Jason:第二个查询的解释显示,覆盖索引将满足查询,但子查询的执行仍然是269622次。(性能是简单的数学运算:无论做计数需要多长时间(1)
使用覆盖索引进行查询,执行次数为269622次。我正在做更多的测试,您的第二次查询似乎非常有效。特别是当我包含WHERE子句(如您的示例)时……最后10天需要~6秒。您所说的计数(1)是什么意思这个代码看起来很有前途。但是,创建一个时间表似乎很奇怪。我是否需要连续地为每分钟插入新的行?而且,在我看来,这会考虑在同一分钟内同时调用,对吗?很明显,一个调用可以在分钟的开始结束,另一个可以在MI结束时开始。nute.@Jason:这也没有考虑通话持续时间少于一分钟的边缘情况,即在同一分钟内开始和停止(不要跨越边界)。但是,这些可能是可以忽略的边缘情况,这可能是一个足够接近的近似值。(我认为这里真正的性能提升是只进行了一天的检查。您还可以在我的回答中添加谓词cl1.StartTime>='2014-05-14'和cl1.StartTime<'2014-05-14'+间隔1天
,测试查询(使用相关子查询),以获得相同的结果。
explain select ts, count(*) from times join cdrs on times.ts between cdrs.starttime and cdrs.stoptime group by ts order by count(*) desc limit 1;
+----+-------------+-------+-------+---------------+---------+---------+------+--------+------------------------------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+-------+---------------+---------+---------+------+--------+------------------------------------------------+
| 1 | SIMPLE | times | index | PRIMARY | PRIMARY | 5 | NULL | 1440 | Using index; Using temporary; Using filesort |
| 1 | SIMPLE | cdrs | ALL | starttime | NULL | NULL | NULL | 260727 | Range checked for each record (index map: 0x4) |
+----+-------------+-------+-------+---------------+---------+---------+------+--------+------------------------------------------------+