以下代码在运行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。

10-05 23:55