以下代码在运行Strawberry Perl 5.24的Windows 10计算机上失败:
use DBI;
unlink glob("*.db3");
my $source = DBI->connect("dbi:SQLite:dbname=first.db3",q(),q(),{AutoCommit => 0, RaiseError =>1});
# populate source DB
$source->do("CREATE TABLE test(x integer)");
$source->do("INSERT INTO test(x) values (1)");
$source->commit();
$source->disconnect();
# copy source to dest
my $dest = DBI->connect("dbi:SQLite:dbname=second.db3",q(),q(),{AutoCommit => 0, RaiseError =>1});
$dest->do("CREATE TABLE test(x integer)");
$dest->do("ATTACH DATABASE 'first.db3' AS chunk_db");
$dest->do("INSERT INTO test(x) SELECT x FROM chunk_db.test");
# this statement will fail when AutoCommit => 0
$dest->commit;
$dest->do("DETACH DATABASE chunk_db");
$dest->disconnect();
为了使其工作,创建
AutoCommit => 1
对象时必须与$dest
连接。否则,我将收到以下错误消息: DBD::SQLite::db do failed: database chunk_db is locked at test.pl line 21.
这是DBI中的错误,还是我做错了什么?
最佳答案
根据documentation,当AutoCommit
模式关闭时:
SQLite的默认事务行为被推迟,这意味着直到第一次读或写操作时才获取锁,因此,另一个线程或进程可能会创建一个单独的事务,并在当前的BEGIN之后写入数据库。线程已执行,并最终导致“死锁”。为避免这种情况,如果您通过调用begin_work或关闭AutoCommit(自1.38_01起)开始事务,则DBD :: SQLite内部会立即发出BEGIN。
如果由于某些原因确实需要关闭此功能,请将sqlite_use_immediate_transaction数据库的handle属性设置为false,然后将使用默认的延迟事务。
(这似乎是不受欢迎的行为,可能是因为我很累,但是我看不到如何获得死锁;尝试锁定已经被另一个连接锁定的数据库时出现错误)
但是无论如何:
$ sqlite3 second.db3
sqlite> attach database 'first.db3' as chunk_db;
sqlite> begin immediate;
sqlite> detach database chunk_db;
Error: database chunk_db is locked
看起来很熟悉...
AutoCommit
处于关闭状态时的默认行为表示您始终与获取的RESERVED lock进行事务。如您所见,这有一些不寻常的副作用。因此,按我的喜好排序的解决方案是:
打开
AutoCommit
模式并手动开始事务(使用$dbh->begin_work
)。由于始终要关闭数据库连接,请跳过
DETACH
。连接到数据库文件时,禁用
AutoCommit
并将sqlite_use_immediate_transaction
选项设置为0。