Как убедиться, что соединение DB (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 и т. Д. Очевидно, он предназначен для веб-приложений.

Есть ли «лучший» способ проверить состояние подключения и при необходимости повторно подключиться? #

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


person toktok    schedule 31.08.2012    source источник
comment
Я решил использовать ваш ответ в комбинации с connect_cached и prepare_cached.   -  person toktok    schedule 01.10.2012


Ответы (3)


Есть ли лучший способ проверить состояние подключения и при необходимости повторно подключиться? #

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

В противном случае у вас все еще есть:

  1. PREPARE
  2. SELECT 1; или как там написано в вашем тестовом заявлении
  3. Отключение сети, сбой серверной части, администратор перезагружает сервер и т. Д.
  4. EXECUTE
  5. splat.

Для правильного поведения требуется что-то вроде псевдокода:

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, чтобы проверить подготовленный вами оператор. Вам нужно либо быть готовым к предупреждению об ошибках у пользователя, либо в любом случае обернуть все это в надлежащую обработку ошибок ... в этом случае нет никакого смысла вообще беспокоиться о проверке времени и тестировании.

person Craig Ringer    schedule 31.08.2012

Вполне разумно выполнить эхо-запрос / протестировать соединение, если вы знаете, что можете позволить соединению бездействовать в течение часа.

Лучше, DBI connect_cached и _ 2_ сделать это относительно легко:

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

person pilcrow    schedule 31.08.2012
comment
Да, нет ничего плохого в периодической проверке незанятых соединений, это просто идея, что вы можете каким-то образом проверить соединение, с которым я не согласен. - person Craig Ringer; 01.10.2012

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

person Borodin    schedule 31.08.2012
comment
Overkill, вероятно, трудное слово, но это будет означать, что мне придется делать это перед каждым выполнением. И у меня на самом деле 3-4 выполнения в цикле. Я не тестировал переподключение, но это, наверное, заставляет меня заново составить заявление? - person toktok; 31.08.2012
comment
Все, что делает этот метод, - это использовать execute как ping, за исключением того, что, если он работает, больше нечего делать. Ответ на неудачный execute будет таким же, как и на неудачный ping: вы должны подключиться к базе данных и снова подготовьте запросы перед выдачей execute - person Borodin; 31.08.2012