как проверить, встречается ли количество записей в БД более x раз

#mysql #sql #perl

#mysql #sql #perl

Вопрос:

У меня есть таблица MySQL, которая содержит записи по следующей схеме:

 hostname | appname | timestamp | message
  

Я использую скрипт perl для запроса базы данных, чтобы получить все записи за последнюю минуту. Этот скрипт активируется каждые 60 секунд.

Скрипт Perl собирает все записи, где временная метка больше, чем текущая временная метка — 60 секунд. Затем я могу проверить, есть ли у меня больше, скажем, 5 записей, что заставляет меня делать некоторые другие вещи в скрипте.

Чего я хочу добиться, так это иметь своего рода «окно» в моем скрипте, чтобы иметь возможность видеть, есть ли перекрытие между ними каждые 60 секунд активации скрипта, чтобы гарантировать, что может быть также более 5 записей.

Я привожу пример:

Скрипт выполняется в 08:42:00, который собирает все записи с 08:41:00 до starttime:

 foo.net spbx 16-06-2014 08:41:51
foo.net spbx 16-06-2014 08:41:55
foo.net spbx 16-06-2014 08:41:57
foo.net spbx 16-06-2014 08:42:59
  

Так что его 4 записи — ничего не поделать.

Скрипт, выполненный в 08:43:00, собирается с 08:42:00 до starttime:

 foo.net spbx 16-06-2014 08:42:01
foo.net spbx 16-06-2014 08:42:02
foo.net spbx 16-06-2014 08:42:03
foo.net spbx 16-06-2014 08:42:04
  

По-прежнему нет ошибки. Но если вы посмотрите точно на «динамический интервал» в 60 секунд, у вас будет более 5 событий, происходящих за интервал в 60 секунд, который не виден при таком простом подходе.

Я думал о том, чтобы всегда собирать последние 120 секунд, а затем проверять от самой старой записи вверх в окне 60s, если событий более 5. Но мне интересно, есть ли какой-нибудь более приятный подход к этой «проблеме»?

Размышляя дальше, если этот динамический подход обнаруживает более 5 событий, тогда ему необходимо удалить эти записи, чтобы не собирать их при следующем выполнении (или просто помечать их в БД как угодно)

Текущий раздел кода выглядит следующим образом:

 my $dbConnect = DBI->connect("DBI:mysql:database=$dbName;host=$dbHost","$dbUser","$dbPass", { RaiseError => 1, AutoCommit => 0})
    or die "ERROR - Can't connect to MySQL-Database: ".$DBI::errstr."n";
debug("Connect to database successfull");

my $dbQuery = $dbConnect->prepare("SELECT message,timestamp FROM $dbTable WHERE hostname='$hostname' AND appname='$appname' AND timestamp > ?");
my $date = Time::Piece->strptime(localtime->epoch-$threshold[1],"%s");
# adding the timezone offset - workaround for FAP-CENTREON
$date  = $date->localtime->tzoffset;
my $starttime = $date->strftime("%Y-%m-%d %H:%M:%S");
debug("query: SELECT message,timestamp FROM $dbTable WHERE hostname=$hostname AND appname=$appname AND timestamp > ".$starttime);
$dbQuery->execute($starttime);

my $amount = $dbQuery->rows;

$alarmMessage = "$amount errors in ".$threshold[1]."s!n";

if($amount < $threshold[0]) {
    $alarmMessage = "$amount errors in ".$threshold[1]."s - this is OK! n";
    $exitCode = 0;
} else {
    while(my @resultrows = $dbQuery->fetchrow_array) {
        $alarmMessage = $alarmMessage.$resultrows[1]." ".$resultrows[0]."n";
    }
    $exitCode = 2;
}

$dbQuery->finish();
$dbConnect->disconnect();

print "$alarmMessage n";
  

Ответ №1:

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

 SELECT a.timestamp, COUNT(*) ct
FROM $dbTable AS a
JOIN $dbTable AS b ON b.timestamp BETWEEN a.timestamp AND DATE_ADD(a.timestamp, INTERVAL 1 MINUTE)
WHERE a.timestamp > DATE_SUB(NOW(), INTERVAL 2 MINUTE)
GROUP BY a.timestamp
HAVING ct > 5
  

Чтобы найти сообщения, которые следует удалить или пометить как замеченные как избыточные, вы можете присоединить этот подзапрос к таблице и выбрать все сообщения, которые находятся в пределах 1 минуты после метки времени.

Комментарии:

1. это не 100% рабочее решение, но оно определенно дало мне несколько отличных советов. Причина, по которой это не 100%, — это лишь некоторые незначительные особенности, касающиеся записей, но они были решены благодаря отличным подсказкам из вашего ответа. Спасибо!

2. mysql> ВЫБЕРИТЕ.timestamp, сообщение.ИЗ $dbTable В КАЧЕСТВЕ ВНУТРЕННЕГО СОЕДИНЕНИЯ $ dbTable как b НА b.timestamp МЕЖДУ.timestamp И DATE_ADD(a.timestamp, ИНТЕРВАЛ 10 МИНУТ), ГДЕ a.timestamp> DATE_SUB(NOW(), ИНТЕРВАЛ 20 МИНУТ) И a.hostname =$hostname И .appname = $appname ГРУППИРУЮТСЯ ПО .timestamp;

Ответ №2:

Комментарий по масштабированию, если вы не возражаете.

Производительность решения только для SQL, объединяющего активно записываемую таблицу с самой собой, не будет хорошо масштабироваться. Если вы действительно ожидаете, что у вас будет порядка 1 строки в секунду, все будет в порядке. Когда вы достигнете 100 строк в секунду, это будет больно.

В этом случае вы, вероятно, захотите иметь периодическую задачу суммирования коротких фрагментов вашей таблицы во вторичную таблицу, а затем сканировать вторичную таблицу на предмет интересных событий. Если ваша вторичная таблица имеет одну строку для каждого 10-секундного фрагмента, вы можете СУММИРОВАТЬ эти подсчеты примерно за постоянное время, независимо от того, сколько событий в исходной таблице.

Если вы обнаружите, что СУММА за минуту фрагментов превышает ваш порог, то, в зависимости от того, действительно ли для вас важно, будет ли n событий отображаться за 59 против 61 секунды, или вам просто нужна приблизительная оценка, вы можете выполнить более конкретный выбор в исходной таблицеэто дает вам точный ответ.

Я писал об этом несколько лет назад, здесь:

http://beta.slashdot.org/journal/93006