2014-12-02 4 views
-1

Я использую PHP и MYSQL диаграмму concurenncy вызова из базы данных Asterisk CDR,улучшить скорость этого запроса MYSQL

настоящего времени я использую следующее подготовленное заявление:

$query=$cdrdb->prepare('select count(acctid) from cdr where calldate between ? and ? or DATE_ADD(calldate, INTERVAL duration SECOND) between ? and ?'); 

, а затем следующий Еогеасп цикл для ввода переменных:

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]); 

    } 

TimeRange может быть каждый час в течение дня, каждый день в течение месяца или каждую неделю в течение года.

столбец calldate помечен как индексный столбец.

В таблице в настоящее время хранится 122 000 записей.

результат выполнения ОБЪЯСНИТЬ по запросу:

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.14s так в течение 24-часового периода с интервалом один час сценарий должен закончить примерно за 3,36 секунды, но это заканчивается тем, что занимает около 12 секунд.

В настоящее время весь процесс может занять до 20 секунд, чтобы работать в течение 24 часов, может ли кто-нибудь помочь мне улучшить скорость этого запроса?

+2

Где ваш вопрос? – cybermonkey

+0

Насколько медленным является запрос? – MonkeyZeus

+0

Добавлена ​​информация к исходному вопросу –

ответ

1

Эта часть является узким местом в вашем запросе:

DATE_ADD(calldate, INTERVAL duration SECOND) 

Это происходит потому, что MySQL выполняет «математику» на каждую строку первого подмножества определяется из первого WHERE состояния каждый ряд на всем вашем таблицу, которая не соответствует первой части вашего WHERE с тех пор, как вы используете WHERE OR, а не WHERE AND.

Я предположил, ваша таблица выглядит немного как:

acctid | calldate   | duration 
======================================== 
1  | 2014-12-01 17:55:00 | 300 
... etc. 

Рассмотрим переписывание вашу схему таким образом, что вы не используете интервалы, которые MySQL должен вычислить для каждой строки, но полные столбцы DateTime, что MySQL может выполнять непосредственный сравнения на:

acctid | calldate   | duration_end 
================================================== 
1  | 2014-12-01 17:55:00 | 2014-12-01 18:00:00 

чтобы переписать эту схему, вы можете сделать этот новый столбец, а затем сделать (это может занять некоторое время, чтобы обработать, но будет служить вам хорошо в долгосрочной перспективе):

UPDATE cdr SET duration_end = DATE_ADD(calldate, INTERVAL duration SECOND); 

Затем скрасить столбец duration и переписать заявку на сохранение в новый столбец!

Ваш результирующий запрос будет:

select count(acctid) from cdr where calldate > ? and (calldate < ? or duration_end between ? and ?) 

Предполагая, что ничего не может изменить в схеме, то вы застряли с этой функцией.Тем не менее, вы можете попытаться иметь MySQL работать с подмножествами, так что это не делает математику на столько строк:

select 
    count(acctid) 
from 
    cdr 
where 
    calldate > ? and 
     (calldate < ? or DATE_ADD(calldate, INTERVAL duration SECOND) between ? and ?) 

Я не могу гарантировать большую часть прироста производительности от этого решения, хотя это может быть заметно один в зависимости от ваш набор данных.

+0

Приложение Asterisk, поэтому изменение его для записи по-другому было бы проблемой (хотя и не невозможной). Я бы предпочел решение, где я могу использовать стандартную структуру таблицы mysql. –

+0

@JurgensKrause Итак, вы хотите улучшить свой запрос, не меняя ничего. [Хорошо известно, что использование функций MySQL на больших диапазонах проблематично.] (Http://stackoverflow.com/questions/3281072/which-mysql-date-query-performs-better) Возможно, вы могли бы подумать о том, чтобы сделать крючок для это приложение, которое сохраняет этот дополнительный столбец при создании строки. Держись за альтернативу, я думаю, что смогу что-то придумать. – sjagr

+0

Asterisk действительно поддерживает пользовательские записи, но я хотел бы сохранить его простым для других людей, которые хотят использовать мое программное обеспечение. Если я не смогу найти лучшую альтернативу, я буду вынужден использовать это решение. Я все равно попробую, и посмотрим, как это работает. Спасибо за предложение. –

0

Для звездочками CDRs вы можете просто сделать, как этот

Пусть говорят, что вы использовали:

$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)) 

Так что первый предельному запроса к интервалу, где это stop_time может быть.

Но все гораздо проще будет добавить столбец call_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 колонке и использовать союз вместо OR (в противном случае одна часть не будет использовать индекс)

0

Если на диске менее важна, чем скорость, попробуйте:

ALTER TABLE cdr ROW_FORMAT = FIXED;