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

当您知道您可能会让连接空闲一个小时时,ping/测试连接是非常合理的。

更好的是,DBIconnect_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 pinging 是通过高效的本机 PostgreSQL 调用实现的。)

于 2012-08-31T14:24:35.550 回答
1

我不明白你为什么说 a pingbefore everyexecute太过分了,但另一种方法是execute通过重新连接、准备语句和execute第二次发出来明确处理失败的情况,因为数据库句柄无效。这会稍微快一点,但我认为没有理由避免该ping策略

于 2012-08-31T10:48:05.087 回答