已解决:DBI catch 语句消失且 CGI::Session 卡住

Posted

技术标签:

【中文标题】已解决:DBI catch 语句消失且 CGI::Session 卡住【英文标题】:Solved: DBI cached statements gone and CGI::Session is stucked 【发布时间】:2017-12-23 14:07:48 【问题描述】:

我正在使用 Apache2.2(worker)/mod_perl 2.0.4/Apache::DBI/CGI::Session 和 Firebird RDBMS。

我还编写了 CGI::Session::Driver::firebird.pm 来使用 Firebird RDBMS。 DB 连接由 Apache::DBI 汇集,并为 CGI::Session Handle=>$dbh 提供连接句柄。

数据库连接数等于工作进程数。

我在 3 个月前发布了 Programming with Apache::DBI and firebird. Get Stucked httpd on exception。 我找到了该问题的原因,并想知道如何解决它。

$dbh = DBI->connect("dbi:Firebird:db=$DBSERVER:/home/cdbs/xxnet.fdb;
ib_charset=UTF8;ib_dialect=3",$DBUSER,$DBPASS,
    AutoCommit=>1,
    LongReadLen=>8192,
    RaiseError=>1
);
my $session = new CGI::Session('dbi:firebird',$sessid,Handle=>$dbh);
my $ses_p1 = $session->param('p1');

eval  $dbh->begin_work()

  my $sql = "SELECT * FROM SAMPLETABLE"
  my $st = $dbh->prepare($sql);
  $st->execute();
  while (my $R = $st->fetchrow_hashref()) 
   ...
  
  $st->finish();
; warn $@ if $@;
if ($@) 
  $dbh->rollback();
else
  $dbh->commit();

$session->flush();

当发生 sql 错误时,eval 块会捕获异常并回滚事务。 之后,CGI::Session 不再检索会话对象。

因为 prepare_cached 语句在 CGI::Session::DBI.pm 处失败。 CGI::Session::DBI.pm 使用 prepare_cached($sql,undef,3)。 '3' 是使用缓存语句最安全的方式,但在这种情况下它永远不会发现损坏的语句。

如何解决这个问题? 提出更改 CGI::Session::DBI.pm 以使用 prepare() 语句的请求? 在firebird.pm中写store(),retrieve(),traverse()函数来使用prepare()语句?

可能其他 prepare_cached() 在 catch 异常后会失败...


1) 我在 CGI::Session->errstr() 上添加了 die 语句 我收到一个错误“new(): failed: load(): could't retrieve data: retrieve(): $sth->execute failed with error message” 2)我在 session->load() 之后刷新会话对象 如果 $session 有效,则将更改存储到 DB。 3) 我将 begin_work() 替换为 AutoCommit=0 结果是一样的。我可以在捕获异常和回滚后正常使用 $dbh,但是新的 CGI::Session 返回错误。 ------------------------------------------ 2017/07/26 18 新增: 47 日产

请给我你的建议。

谢谢。

【问题讨论】:

也许session->flush 会杀死会话?也许有人会从$sessid 中删除任何值? $session->flush() 不会引发任何异常。在 $session->flush() 之后,另一个查询运行良好,但下一个“new CGI::Session()”返回空。 CGI::Session->load() 返回 undef。 我也通过删除 $session->flush() 对其进行了测试,但下一个 CGI:Session 返回为空。 你为什么又打电话给new CGI::Session()?您是否需要在同一个脚本中检索不同的会话? ... $session->flush() 不会引发异常,除非您告诉它,您可以添加 $session->flush() or die "Unable to update session storage!" 并查看是否引发异常 示例代码不会单独引发错误。这是在web系统中,connect()语句在通用包和池连接中。因此,每个网页屏幕(cgi 程序)在开始时都会调用 new CGI::Session() 并检索 $session 并使用它进行处理。我的问题是“在某些屏幕上发生 sql 错误后,在所有屏幕上检索都失败”。 【参考方案1】:

在请求更改 CGI::Session::Driver::DBI.pm 之前,您可以尝试多种方法...

首先,更改new CGI::Session 的调用方式,以诊断是否在创建或加载会话时出现问题:

my $session = CGI::Session->new('dbi:firebird',$sessid,Handle=>$dbh) or die CGI::Session->errstr();

paramdelete 方法将会话更改存储在 $session 句柄中,而不是在 DB 中。 flush 将会话句柄内所做的更改存储在 DB 中。仅在会话->参数设置/更新或会话删除后使用 $session->flush()

$session->param('p1','someParamValue');
$session->flush() or die 'Unable to update session storage!';

# OR
$session->delete();
$session->flush() or die 'Unable to update session storage!';

flush 方法不会破坏$session 句柄(您仍然可以在刷新后调用$session->param('p1'))。在某些情况下 mod_perl 缓存 $session 导致下次尝试加载同一会话时出现问题。在这些情况下,它需要在不再需要时将其销毁:

undef($session)

我可以建议的最后一件事是避免使用begin_work 方法,而是使用AutoCommit 控制事务行为(因为DBD::Firebird documentation 表示应该控制事务的方式)和eval 块内的commit

eval 
    # Setting AutoCommit to 0 enables transaction behavior
    $dbh->AutoCommit = 0;

    my $sql = "SELECT * FROM SAMPLETABLE"
    my $st = $dbh->prepare($sql);
    $st->execute();

    while (my $R = $st->fetchrow_hashref()) 
        ...
        

    $st->finish();
    $dbh->commit();
    ;
if ($@) 
     warn "Tansaction aborted! $@";
     $dbh->rollback();
     

# Remember to set AutoCommit to 1 after the eval
$dbh->AutoCommit = 1;

您说您为 Firebird 编写了自己的会话驱动程序...您应该看看 CGI/Driver/sqlite.pm 或 CGI/Driver/mysql.pm 是如何制作的,也许您需要编写一些您缺少的获取方法...

希望这会有所帮助!

【讨论】:

1) 添加 CGI::Session->errstr(): new(): failed: load(): 无法检索数据:retrieve(): $sth->execute failed with error消息 2) 刷新会话对象:如果 $session 有效,则将更改存储到 DB。 3)使用 AutoCommit=0 和其他:情况相同。我可以在捕获异常和回滚后正常使用 $dbh,但是新的 CGI::Session 返回错误。 --- 我的 Driver/firebird.pm 只有 init() 和 table_name(),然后在 DBI.pm 中使用 store/retrieve。我写了 firebird.pm 来加载 DBD::Firebird 并将 DSN 设置为 'dbi:firebird'。 我在 firebird.pm 中添加了没有“_cached”的存储、检索、遍历。它看起来工作正常。 mysql.pm 有 mk_dsn,init,table_name,store。 store() 更改为使用 DBI.pm 中的“ON DUPLICATES”。 DBI->begin_work() 将 AutoCommit 更改为 0。我找到了解决方法。那么,我想知道为什么缓存语句不见了? 你能用你发现的错误更新你的问题吗?

以上是关于已解决:DBI catch 语句消失且 CGI::Session 卡住的主要内容,如果未能解决你的问题,请参考以下文章

Centos7,桌面图标消失且右键失效

Perl DBI - 使用多个语句运行 SQL 脚本

Try...catch:测试语句是不是为真

Perl DBI 语句句柄和错误处理

Perl DBI - 获取事务中每个语句影响的记录

当 Perl 的 DBI 在准备语句时遇到错误时,如何避免程序退出?