Php 提高MYSQL查询的速度
我使用PHP和MYSQL从Asterisk CDR数据库绘制调用Concurrency的图表 我目前使用以下准备好的声明:Php 提高MYSQL查询的速度,php,mysql,asterisk,Php,Mysql,Asterisk,我使用PHP和MYSQL从Asterisk CDR数据库绘制调用Concurrency的图表 我目前使用以下准备好的声明: $query=$cdrdb->prepare('select count(acctid) from cdr where calldate between ? and ? or DATE_ADD(calldate, INTERVAL duration SECOND) between ? and ?'); 然后使用以下foreach循环输入变量: foreach (
$query=$cdrdb->prepare('select count(acctid) from cdr where calldate between ? and ? or DATE_ADD(calldate, INTERVAL duration SECOND) between ? and ?');
然后使用以下foreach循环输入变量:
foreach ($timerange as $startdatetime){
$start=$startdatetime->format("Y-m-d H:i:s");
$enddatetime=new DateTime($start);
$enddatetime->Add($interval);
$end=$enddatetime->format("Y-m-d H:i:s");
if(!$query->execute(array($start, $end, $start, $end))){
echo "Execute failed: (" . $stmt->errno . ") " . $stmt->error;
}
if (!($res = $query->fetchall())) {
echo "Getting result set failed: ";
}
array_push($callsperinterval,$res[0][0]);
}
时间范围可以是一天中的每小时、一个月中的每一天或一年中的每周
calldate列被标记为索引列
该表目前保存了122000条记录
对查询运行解释的结果:
mysql> explain select count(acctid) from cdr where calldate between '2014-10-02 23:30:00' and '2014-11-03 00:00:00' or DATE_ADD(calldate, INTERVAL duration SECOND) between '2014-10-02 23:30:00' and '2014-11-03 00:00:00';
+----+-------------+-------+------+---------------+------+---------+------+--------+-------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+------+---------------+------+---------+------+--------+-------------+
| 1 | SIMPLE | cdr | ALL | calldate | NULL | NULL | NULL | 123152 | Using where |
+----+-------------+-------+------+---------------+------+---------+------+--------+-------------+
单次运行查询大约需要0.14秒,因此在24小时的时间段内(每小时一次),脚本应该在3.36秒左右完成,但最终大约需要12秒
目前,整个过程最多需要20秒才能运行24小时,是否有人可以帮助我提高此查询的速度?此部分是您查询中的瓶颈:
DATE_ADD(calldate, INTERVAL duration SECOND)
这是因为MySQL正在对根据第一个WHERE
条件确定的第一个子集的每一行执行“数学运算”,因为您使用的是WHERE或,而不是WHERE和
,所以整个表中的每一行都与WHERE
语句的第一部分不匹配
我想你的桌子看起来有点像:
acctid | calldate | duration
========================================
1 | 2014-12-01 17:55:00 | 300
... etc.
考虑重写模式,这样您就不会使用MySQL必须为每一行计算的时间间隔,而是使用MySQL可以立即对其执行比较的完整DateTime列:
acctid | calldate | duration_end
==================================================
1 | 2014-12-01 17:55:00 | 2014-12-01 18:00:00
要重写此模式,您可以创建新列,然后执行此操作(这可能需要一段时间来处理,但从长远来看会很好地为您服务):
然后废弃duration
列并重写应用程序以保存到新列中
您的查询结果将是:
select count(acctid) from cdr where calldate > ? and (calldate < ? or duration_end between ? and ?)
从cdr where calldate>中选择计数(acctid)?和(通话日期<或持续时间结束于?和?)
假设模式中没有任何东西可以更改,那么您就只能使用该函数了。但是,您可以尝试让MySQL处理子集,这样它就不会对这么多行进行计算:
select
count(acctid)
from
cdr
where
calldate > ? and
(calldate < ? or DATE_ADD(calldate, INTERVAL duration SECOND) between ? and ?)
选择
计数(acctid)
从…起
cdr
哪里
calldate>?和
(calldate<?或DATE_ADD(calldate,间隔持续时间秒)介于?和?之间)
我不能保证此解决方案的性能会有很大的提高,尽管根据您的数据集可能会有明显的提高。对于asterisk CDR,您可以这样做
$query=$cdrdb->prepare('select count(acctid) from cdr where calldate between ? and DATE_ADD(?, interval ? SECOND) and (calldate between ? and ? or DATE_ADD(calldate, INTERVAL duration SECOND) between ? and ?)
');
$MAX_CALL_LENGHT_POSIBLE = 60*60*10; # usualy 10 hr is not reachable on most calls. If you limit it in call, you can decrease to even less values
$query->execute(array($start, $end,$MAX_CALL_LENGHT_POSIBLE,$start,$end $start, $end))
假设您使用了:
$query=$cdrdb->prepare('select count(acctid) from cdr where calldate between ? and ? or DATE_ADD(calldate, INTERVAL duration SECOND) between ? and ?');
$query->execute(array($start, $end, $start, $end))
你有这样的用途吗
$query=$cdrdb->prepare('select count(acctid) from cdr where calldate between ? and DATE_ADD(?, interval ? SECOND) and (calldate between ? and ? or DATE_ADD(calldate, INTERVAL duration SECOND) between ? and ?)
');
$MAX_CALL_LENGHT_POSIBLE = 60*60*10; # usualy 10 hr is not reachable on most calls. If you limit it in call, you can decrease to even less values
$query->execute(array($start, $end,$MAX_CALL_LENGHT_POSIBLE,$start,$end $start, $end))
所以,只需首先将查询限制为停止时间所在的时间间隔
但更简单的是添加列调用_end _time和创建触发器
DROP TRIGGER IF EXISTS cdr_insert_trigger;
DELIMITER //
CREATE TRIGGER cdr_insert_trigger BEFORE INSERT ON cdr
FOR EACH ROW BEGIN
Set NEW.call_end_time=DATE_ADD(OLD.calldate,interval OLD.duration second);
END//
DELIMITER ;
确实需要在calldate和call_end_time列上创建索引,并使用Union而不是OR(否则一部分将不使用索引)如果磁盘空间不如速度重要,请尝试:
ALTER TABLE cdr ROW_FORMAT = FIXED;
你的问题在哪里?查询速度有多慢?在原始问题中添加了信息。那么,我认为你在一次又一次地运行同一个查询对吗?请注意:
DATE\u ADD(calldate,INTERVAL duration SECOND)
是您的性能瓶颈,因为它正在对每个子集行执行计算以进行比较。应用程序是星号,因此将其更改为以不同方式写入将是一个挑战(尽管并非不可能)。我更喜欢一个可以使用标准mysql表结构的解决方案。@JurgensKrause,因此您希望在不更改任何内容的情况下改进查询。也许你可以考虑为这个应用程序制作一个钩子,在创建行时保存额外的列。还有一个选择,我想我可以想出一些办法。星号实际上支持自定义条目,但我想对其他想使用我的软件的人保持简单。如果我找不到更好的替代方案,我将被迫使用这个解决方案。无论如何,我都会尝试一下,看看它的性能如何。谢谢你的建议。第二种选择效果很好。它将脚本时间缩短到3秒左右。非常感谢你!星号cdr的通话结束时间栏没有任何挑战。只需使用简单的触发器。