Warning: file_get_contents(/data/phpspider/zhask/data//catemap/1/php/236.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181

Warning: file_get_contents(/data/phpspider/zhask/data//catemap/8/mysql/62.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Php 提高MYSQL查询的速度_Php_Mysql_Asterisk - Fatal编程技术网

Php 提高MYSQL查询的速度

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 (

我使用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 ($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的通话结束时间栏没有任何挑战。只需使用简单的触发器。