6

我正在尝试对我们的一些遗留代码进行排序,这些代码有两个目的。它使用 DBI创建数据库,然后使用 DBI连接到该数据库。不幸的是,它对每个都使用了相同的代码。这意味着如果您创建一个sales数据库,稍后在使用 reconnect 时,您必须显式调用$dbh->do('use sales'). 这会导致各种问题,例如开发人员忘记这样做或数据库处理重新连接并忘记它在哪个数据库中。

作为第一次修复,我们尝试做的是在数据库不存在时DBI::connect()使用该方法重新连接到 MySQL,从而允许我们创建数据库。HandleError由于各种遗留原因(是的,我们都去过那里),尝试在connect()方法之外捕获“未知数据库”错误要困难得多。

因此,我第一次解决这个问题如下:

use strict;                                                                                                                                               
use warnings;
use DBI;
use PadWalker 'peek_my';
my $dbh = DBI->connect(
    $dsn,
    $user,
    $pass,
    {   RaiseError  => 1,
        PrintError  => 0,
        HandleError => \&reconnect_if_unknown_database,
    },
);

sub reconnect_if_unknown_database {
    my ($msg, $drh, $dbh) = @_;
    return unless $msg =~ /Unknown database/;

    my ($dsn, $user, $pass, $attr) = @{peek_my(1)}{qw/$dsn $user $pass $attr/};

    unless ($dsn && $user && $pass && $attr) {
        return;    # don't do this if we can't get everything
    }

    # they're all scalar refs.
    $_ = $$_ foreach $dsn, $user, $pass, $attr;

    unless ($dsn =~ s/^[^;]+;/DBI:mysql:mysql;/) {
        return;    # can't parse dsn, so return
    }
    delete $attr->{HandleError};    # infinite loops tickle

    $_[2] = DBI->connect($dsn, $user, $pass, $attr);
}

这行得通,而且它目前对最终用户是透明的,但它也感觉像是一堆热气腾腾的 1 和 0。有没有更好的方法在连接失败时重新连接到不同的数据库?

4

1 回答 1

7

我不确定这是否可行,但它可能比使用更好PadWalker

use strict;use warnings;
use DBI;

my %attr = (RaiseError => 1, PrintError => 0);

my $dbh = DBI->connect(
    $dsn,
    $user,
    $pass,
    {
        %attr,
        HandleError => sub {
            reconnect_if_unknown_database(
                $dsn, $user, $pass, \%attr, @_
            )
        },
    },
);

sub reconnect_if_unknown_database {
    my ($dsn, $user, $pass, $attr, $msg, $drh, $dbh) = @_;

    return unless $msg =~ /Unknown database/;

    return unless $dsn =~ s/^[^;]+;/DBI:mysql:mysql;/;

    $_[-1] = DBI->connect($dsn, $user, $pass, $attr);
}
于 2012-05-04T14:36:49.497 回答