3

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 などに依存していることがわかります。明らかに Web アプリケーション向けです。

接続状態をチェックし、必要に応じて再接続する「ベストプラクティス」の方法はありますか?#

すべてのループでステートメントを準備できますが、それは解決策ではなく、問題を回避する方法です。

4

3 に答える 3

6

接続状態を確認し、必要に応じて再接続するための「ベストプラクティス」の方法はありますか?#

はい、少なくとも私の見解では、競合状態のないアプローチは1つしかなく、エラーが発生した場合にエラーを処理する再試行ループでクエリを実行するためです。

それ以外の場合はまだあります:

  1. PREPARE
  2. SELECT 1;またはあなたのテストステートメントが何であれ
  3. ネットワークがドロップアウトし、バックエンドがクラッシュし、管理者がサーバーを再起動します。
  4. EXECUTE
  5. 感嘆符。

正しい動作には、擬似コードのようなものが必要です。

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して、準備されたステートメントを確認します。ユーザーにエラーを報告する準備をするか、とにかくすべてを適切なエラー処理でラップする必要があります...この場合、時間のチェックとテストに煩わされることはまったくありません。

于 2012-08-31T11:24:11.093 に答える
3

接続を1 時間アイドル状態にする可能性があることがわかっている場合は、接続を ping/テストすることは非常に合理的です。

より良いのは、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 ping が、効率的なネイティブ PostgreSQL 呼び出しで実装されていることです。)

于 2012-08-31T14:24:35.550 に答える
1

pingbefore everyexecuteがやり過ぎだと言っている理由がわかりませんが、別の方法としてexecute、再接続し、ステートメントを準備して、もう一度発行することで、データベース ハンドルが無効であるために失敗した場合を明示的に処理することexecuteです。これはわずかに高速ですが、ping戦略を回避する理由はありません

于 2012-08-31T10:48:05.087 に答える