DBI:评估中的引发错误

Posted

技术标签:

【中文标题】DBI:评估中的引发错误【英文标题】:DBI: raiseerror in eval 【发布时间】:2012-09-20 23:54:53 【问题描述】:

这个问题参考了池上的这条评论:

[...] But if you're going to put an eval around every statement, just use RaiseError => 0. [...]

在这个thread.

如果在这种情况下将RaiseError 设置为0,我会得到什么?

#!/usr/bin/env perl
use warnings;
use 5.10.1;
use DBI;

my $db = 'my_test_sqlite_db.sqlite';
open my $fh, '>', $db or die $!;
close $fh or die $!;

my ( $dbh, $sth );
eval 
    $dbh = DBI->connect( "DBI:SQLite:dbname=$db", "", "",  );
;
if ( $@ )  print $@ ;

my $table = 'my_sqlite_table';

say   "RaiseError = 1";
say   "PrintError = 0";
$dbh->RaiseError = 1;
$dbh->PrintError = 0;
eval 
    $sth = $dbh->prepare( "SELECT * FROM $table" );
    $sth->execute();
;
if ( $@ )  print "ERROR: $@" ;

say "\nRaiseError = 0";
say   "PrintError = 1";
$dbh->RaiseError = 0;
$dbh->PrintError = 1;
eval 
    $sth = $dbh->prepare( "SELECT * FROM $table" );
    $sth->execute();
;
if ( $@ )  print "ERROR: $@" ;

say "\nRaiseError = 0";
say   "PrintError = 0";
$dbh->RaiseError = 0;
$dbh->PrintError = 0;
eval 
    $sth = $dbh->prepare( "SELECT * FROM $table" );
    $sth->execute();
;
if ( $@ )  print "ERROR: $@" ;

输出:

RaiseError = 1
PrintError = 0
ERROR: DBD::SQLite::db prepare failed: no such table: my_sqlite_table at ./perl2.pl line 23.

RaiseError = 0
PrintError = 1
DBD::SQLite::db prepare failed: no such table: my_sqlite_table at ./perl2.pl line 33.
ERROR: Can't call method "execute" on an undefined value at ./perl2.pl line 34.

RaiseError = 0
PrintError = 0
ERROR: Can't call method "execute" on an undefined value at ./perl2.pl line 44.

【问题讨论】:

这是预期的行为 - DBI 返回 undef(因为异常被关闭); Perl 不知道如何处理这个 undef 值并且死掉了。问题是什么? 【参考方案1】:

如果由于某些原因失败,大多数 $dbh 方法会:

(如果RaiseError选项设置为0)返回undef (如果 RaiseError 选项设置为 1)立即退出脚本('die'),错误原因作为退出消息给出。

这里的关键是如何处理错误取决于您。如果您愿意,您可以忽略它们,例如(以下显然仅适用于 RaiseError 设置为 0):

for my $db ( ... ) 
    my $dbh = get_database_handle( $db )
       or next;
    ...

在这个 sn-p 中(复制自您在问题中提到的@ikegami 的回答),您循环浏览一些数据库连接设置列表;如果某个连接给了你一个undef,你就去找另一个,并且什么都不做。

不过,通常情况下,当错误发生时,您必须做的不仅仅是“下一步”——但话又说回来,您有两个选择:要么检查 each $dbh-related 声明,像这样:

$sth = $dbh->prepare('some_params') 
  or process_db_error('In prepare');
...
$res = $sth->execute('another_set_of_params') 
  or process_db_error('In execute');
...
$res->doAnythingElse('something completely different') 
  or process_db_error('In something completely different');

(因为 or 部分仅在其对应的“左侧部分”在布尔上下文中评估为 false 时才会执行)

...或者只是将所有这些包装到 Perlish 'try-catch' 块中:

if (!eval     
   $sth = $dbh->prepare('some_params');
   ...
   $res = $sth->execute('another_set_of_params');
   ...
   $res->doSomethingElse('something completely different') 
   ...
   1  # No exception
) 
   process_db_error($@);

选择什么,由您决定:这是“返回语句中的错误”(除了要获取实际错误,您必须询问 $dbh 对象)和异常之间的常见决定。

但底线是你不能只写这个:

$sth = $dbh->do_something('that_can_result_in_error');
$sth->do_something('else');

...如果您确实将RaiseError 设置为0。在这种情况下脚本不会死,$sth 将被分配一个undef,你会得到一个“衍生”错误(因为你不能在undef 上调用方法)。

这正是您原始问题的最后一部分代码中发生的事情。

【讨论】:

以上是关于DBI:评估中的引发错误的主要内容,如果未能解决你的问题,请参考以下文章

perl 中的 DBD::Oracle 和 DBI 错误

你能防止 Lua 中的短路评估吗?

SQL 编译错误:无法评估不受支持的子查询类型 - SELECT 子句中的函数调用

Linux中perl-DBI和DBI有啥区别?

用于 Logistic 回归评估的 Sklearn Python Log Loss 引发错误

用于Logistic回归评估的Sklearn Python Log Loss引发了错误