如果使用事务,具有多个数据库句柄的 Perl DBI SQLite 脚本会因数据库锁定而失败

问题描述 投票:0回答:1

我有一个使用 Perl 的 DBI 模块和 SQLite 驱动程序的应用程序。该应用程序处理一些图像,并将有关图像内容的数据存储在数据库中。对于某些用途,数据库中的数据可以在更多图像进入后被丢弃,但在当前实例中,我想归档信息以防止主数据库变得太大。我将相关行从“管道”数据库移动到“存档”数据库。我发现,如果我为存档数据库启用事务,该过程会失败并出现“数据库已锁定”错误。这看起来与这个问题相似,但又不同。如下所示,我使用单个进程来访问数据库。

这是问题的 MWE。我使用一张表创建一个简单的“管道”数据库。我创建了一个具有相同架构的“存档”数据库。我将一些条目从工作“管道”数据库移动到“存档”数据库。

use strict;
use warnings;
use DBI;

unlink foreach glob('*.sdb');

#create the table in the pipeline db
my $dbname_pipe = 'pipeline.sdb';
my $dbh_pipe = DBI->connect("dbi:SQLite:dbname=$dbname_pipe","","",{RaiseError=>1,AutoCommit=>1}) or die $DBI::errstr;
my $sth_pipe = $dbh_pipe->prepare('CREATE TABLE files (filename varchar(128) NOT NULL, frame int, PRIMARY KEY(filename))') ;
$sth_pipe->execute();

#add some data to the pipeline table
$sth_pipe = $dbh_pipe->prepare('INSERT INTO files (filename, frame) VALUES (?, ?)');
$sth_pipe->execute(sprintf("file_%04d.png",$_),$_) foreach (1..20);

#create the table in the archive db
my $dbname_arch = 'archive.sdb';
my $dbh_arch = DBI->connect("dbi:SQLite:dbname=$dbname_arch","","",{RaiseError=>1,AutoCommit=>1}) or die $DBI::errstr;
my $sth_arch = $dbh_arch->prepare('CREATE TABLE files (filename varchar(128) NOT NULL, frame int, PRIMARY KEY(filename))') ;
$sth_arch->execute();

#move some entries from the pipeline db to the archive db
$dbh_arch->do(qq{ATTACH DATABASE "$dbname_pipe" AS pipeline});

$dbh_pipe->begin_work;
$dbh_arch->begin_work;
eval {
    $sth_arch = $dbh_arch->prepare('INSERT INTO files(filename, frame) SELECT filename, frame FROM pipeline.files WHERE frame < ?');
    $sth_pipe = $dbh_pipe->prepare('DELETE FROM files WHERE frame < ?');
    $sth_arch->execute(10);
    $sth_pipe->execute(10);
    $dbh_arch->commit;
    $dbh_pipe->commit;
};
if ($@) {
    warn "archiving transaction aborted because of $@";
    eval { $dbh_pipe->rollback };
    eval { $dbh_arch->rollback };
}

$dbh_arch->do(qq{DETACH DATABASE pipeline});
#disconnect
$dbh_arch->disconnect;
$dbh_pipe->disconnect;

#done
1;

上面的代码失败并显示

DBD::SQLite::st execute failed: database is locked at dbi_lock_mwe.pl line 32. archiving transaction aborted because of DBD::SQLite::st execute failed: database is locked at dbi_lock_mwe.pl line 32.

如果我通过注释掉各个行来禁用存档数据库的事务:

$dbh_arch->begin_work;
$dbh_arch->commit;
eval { $dbh_arch->rollback };

然后脚本完成,没有错误,并且两个 .sdb 文件中的表是正确的:帧 1-9 位于 archive.sdb 文件中,帧 10-20 保留在 pipeline.sdb 文件中。

如何在仍使用事务的情况下防止存档数据库锁定?

sqlite perl transactions dbi
1个回答
0
投票

DBD::SQlite 中有一个名为

sqlite_use_immediate_transaction
的设置,可以在获取锁时进行更改。

SQLite的默认事务行为是延迟的,这意味着直到第一次读或写操作时才会获取锁,因此另一个线程或进程可能会在BEGIN之后创建一个单独的事务并写入数据库当前线程已经执行完毕,最终造成“死锁”。为了避免这种情况,如果您通过调用 begin_work 或关闭 AutoCommit(自 1.38_01 起)开始事务,DBD::SQLite 会在内部发出 BEGIN IMMEDIATE。

如果您为数据库句柄的

both 设置此值,则您的代码可以正常工作。这是两者之一作为示例。

my $dbh_pipe = DBI->connect( "dbi:SQLite:dbname=$dbname_pipe", "", "", { RaiseError => 1, AutoCommit => 1, sqlite_use_immediate_transaction => 0, }) or die $DBI::errstr;
我不明白为什么会这样,但我怀疑如果没有这个设置,锁定会以奇怪的顺序发生,然后驱动程序会感到困惑。

运行这样的代码后,您最终会在存档中得到文件 1 到 9,在管道中得到文件 10 到 20。

© www.soinside.com 2019 - 2024. All rights reserved.