Как убедиться, что соединение между БД (Postgres) еще остается между подготовкой и выполнением?
У меня есть скрипт-демон, который всегда работает в цикле while. У меня есть подготовленный оператор, и этот оператор выполняется в каждом цикле.
Пример:
my $dbh;
sub get_dbh {
return DBI->connect(...);
}
my $dbh = get_dbh();
my $sth = $dbh->prepare("SELECT ....") or die $DBI::errstr;
while (1) {
// is connection still there??
//if (!$dbh->ping) {
// $dbh = get_dbh();
//}
$sth->execute('param1');
// do some other things ... sleep between 0 and 3600
}
Проблема возникает (или может возникнуть), если подготовленное заявление подготовлено несколько часов назад. Связь может умереть, и я тоже могу выполнить. Проверка $dbh->ping перед каждым выполнением выглядит как перебор.
MySQL поддерживает mysql_auto_reconnect, который действительно работает. DBD::Pg не имеет ничего подобного. Я читал о DBI::Apache, но, как я вижу, это зависит от mod_perl и т. Д. Он явно предназначен для веб-приложений.
Есть ли лучший метод проверки состояния соединения и повторного подключения при необходимости?
Я мог бы подготовить утверждение для каждого цикла, но это не решение, а просто способ обойти проблему.
3 ответа
Есть ли лучший метод проверки состояния соединения и повторного подключения при необходимости?
Да, по крайней мере, на мой взгляд, потому что есть только один подход, свободный от условий гонки, который заключается в выполнении запроса в цикле повторов, который обрабатывает ошибки, если они возникают.
В противном случае у вас еще есть:
PREPARE
SELECT 1;
или что-то еще- Сеть выпадает, серверные сбои, администратор перезапускает сервер, что угодно
EXECUTE
- восклицательный знак.
Правильное поведение требует что-то вроде псевдокода:
while not succeeded:
try:
execute_statement()
succeeded = True
except some_database_exception:
if transaction_is_valid():
// a `SELECT 1` or `select 1 from pg_prepared_statements where name = 'blah'
// succeeded in transaction_is_valid(), so the issue was probably
// transient. Retry, possibly with a retry counter that resets the
// connection if more than a certain number of retries.
// It can also be useful to examine the exception or error state to
// see if the error is recoverable so you don't do things like retry
// repeatedly for a transaction that's in the error state.
else if test_connection_usable_after_rollback():
// Connection is OK but transaction is invalid. You might determine
// this from the exception state or by seeing if sending a `ROLLBACK`
// succeeds. In this case you don't have to re-prepare, just open
// a new transaction. This case is not needed if you're using autocommit.
else:
// If you tried a SELECT 1; and a ROLLBACK and neither succeeded, or
// the exception state suggests the connection is dead. Re-establish
// it, re-prepare, and restart the last transaction from the beginning.
reset_connection_and_re_prepare()
Многословно и раздражает? Да, но обычно легко оборачивается помощником или библиотекой. Все остальное еще подвержено гонкам.
Самое главное, что если ваше приложение выдает транзакции, когда оно выполняет более одной операции, оно должно помнить все, что оно делало до фиксации транзакции, и иметь возможность повторить всю транзакцию в случае ошибки. Это, или скажите пользователю "ой, я съел ваши данные, пожалуйста, введите его еще раз и попробуйте снова".
Если вы не возражаете против гонок и просто хотите обрабатывать любые явно неработающие соединения с периодической проверкой, просто сохраните время последнего запроса в переменной. При выдаче запросов проверьте, если отметка времени старше нескольких минут, и если это так, введите SELECT 1;
или запрос против pg_prepared_statements
проверить ваше подготовленное заявление. Вам либо нужно быть готовым к тому, чтобы отвергать ошибки у пользователя, либо в любом случае обернуть все это в правильную обработку ошибок... в этом случае нет смысла вообще беспокоиться о проверке и тестировании времени.
Вполне разумно пропинговать / тестировать соединение, когда вы знаете, что можете позволить соединению простаивать в течение часа.
Лучше, DBI connect_cached
а также prepare_cached
сделать это относительно легко:
while (1) {
my $dbh = DBI->connect_cached(..., { RaiseError => 1 }); # This will ping() for you
my $sth = $dbh->prepare_cached('SELECT ...');
$sth->execute('param1');
# Do work, sleep up to 1 hour
}
Таким образом, вы будете повторно использовать одно и то же подготовленное утверждение в течение срока действия соединения.
(Для чего бы это ни стоило, современный пинг DBD::Pg реализован с помощью эффективного, собственного вызова PostgreSQL.)
Я не понимаю, почему вы говорите, что ping
перед каждым execute
является излишним, но альтернатива заключается в явном execute
сбой, потому что дескриптор базы данных является недопустимым путем повторного подключения, подготовки оператора и выдачи execute
второй раз. Это будет немного быстрее, но я не вижу причин, чтобы избежать ping
стратегия