DBD::SQLite: 数据库被锁定:如何重试?

4

我同时使用SQLite数据库。主要用于读取数据,这意味着一切都很顺利。但是如果涉及到写入和删除表格,就会在随机时间出现下面的错误(这表明存在竞态条件 - 这在并行运行时是可以预料的):

Error: near line 1: database is locked

现在我知道数据库不会被锁定,需要等待10毫秒再尝试,但我找不到捕获错误的方法。

我该如何捕获这个错误?


如果您正在使用SQLite包装器,那么应该设置“繁忙超时”(请参阅SQLite文档),SQLite会自动处理此问题。建议的值似乎在5-10秒左右(即5000-10000的值)。 - TripeHound
问题似乎在于它没有被视为“忙”,而是被视为“锁定”,并且它错误地假设锁定不会消失。 - Ole Tange
2
可以在这里阅读到: http://www.sqlite.org/cvstrac/wiki?p=DatabaseIsLockedSQLITE_LOCKED错误与SQLITE_BUSY(5)不同。 SQLITE_BUSY表示另一个数据库连接(可能在另一个进程中)正在以防止您使用它的方式使用数据库。 SQLITE_LOCKED表示争用的来源是内部的,并来自收到SQLITE_LOCKED错误的相同数据库连接。因此,等待和重试可能无法帮助。 - Georg Mavridis
1
我不知道你是否遇到了这个问题,但是因为你提到了“并行”,所以我想提出来。最近我也遇到了这个问题,发现问题在于我在fork()下同时使用了两个独立的数据库句柄。当我重构代码,改为传递单个句柄并且放弃第二个句柄时,问题就解决了。这在文档中有记录。 - stevieb
@GeorgMavridis:这很重要,谢谢。我以为“locked”错误就像文件系统锁定一样,可以由任何进程独立应用。听起来OP的问题是所有子进程都在使用同一个数据库连接,因此相互锁定。 - Borodin
@stevieb:你所提到的文档非常有用,但它所说的与你的结论相反。我读到了“如果你需要fork(),在fork()之后重新打开数据库。根据你的需求,你可能还想调整sqlite_busy_timeoutsqlite_use_immediate_transaction(见下文)。”这对我来说意味着,如果你没有多个数据库句柄,问题就会出现。 - Borodin
1个回答

0

更新

请注意上面Georg Mavridis的评论

听起来你的子进程正在共享同一个数据库连接并相互锁定

如果你想要真正的并行处理,那么你需要建立多个数据库连接。SQLite会为你从不同的连接请求中排队并解决冲突,除非该行为被禁用。



您需要设计DBI应用程序的错误处理。在connect调用中可以指定三个选项:

  • PrintError - 默认开启 - 如果出现错误,将发出一个警告

  • RaiseError - 默认关闭 - 如果出现错误,将导致进程终止

  • HandleError - 默认未设置 - 必须将此选项设置为一个子例程引用,如果出现错误,则将调用该子例程

如果您不希望出现数据库错误,则最好使用

my $dbh = DBI->connect( ..., { PrintError => 0, RaiseError => 1 } )

然后,您可以为代码的某些部分启用错误处理,以防出现问题并尝试修复它

DBI文档中的RaiseError选项如此说明

If you want to temporarily turn RaiseError off (inside a library function that is likely to fail, for example), the recommended way is like this:

{
  local $h->{RaiseError};  # localize and turn off for this block
  ...
}

这样,在闭合括号处隐式地重新打开了RaiseError选项,您可以在块内检查execute返回的值,该值指示操作是否成功,以及errstr提供的错误类型细节。然后,您可以编写Perl重试代码来执行任何您想要的操作。

标准的sleep调用将挂起进程,但粒度为一秒。如果您不能承受程序在重试之间等待那么长时间,则可以查看Time::HiRes模块中的usleep函数,它以一微秒的倍数进行计算。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接