【问题标题】:DBD::SQLite: database is locked: How to retry?DBD::SQLite:数据库被锁定:如何重试?
【发布时间】:2017-04-29 20:05:29
【问题描述】:

我并行使用 SQLite 数据库。主要用于阅读——这意味着一切都很好。但也用于写入和删除表格。然后突然我在随机时间得到这个(这表明竞争条件 - 预计会并行运行):

Error: near line 1: database is locked

现在我知道在 10 毫秒内数据库不会被锁定,所以我想等待 10 毫秒再试一次,但我找不到捕获该错误的方法。

我怎样才能发现这个错误?

【问题讨论】:

  • 如果在你使用的 SQLite 包装器中有办法做到这一点,你可能应该设置一个“忙超时”(参见SQLite docs),SQLite 应该处理这个问题。建议值似乎在 5-10s 左右(即 5000-10000 的值)。
  • 问题似乎是它没有被视为 BUSY,而是被视为 LOCKED,并且它假设(错误地)锁定不能消失。
  • 可以在此处阅读:sqlite.org/cvstrac/wiki?p=DatabaseIsLocked SQLITE_LOCKED 错误与 SQLITE_BUSY (5) 不同。 SQLITE_BUSY 表示另一个数据库连接(可能在另一个进程中)正在以阻止您使用它的方式使用数据库。 SQLITE_LOCKED 表示争用源是内部的,并且来自收到 SQLITE_LOCKED 错误的同一数据库连接。所以等待和重试可能无济于事。
  • 我不知道这是否是您遇到的问题,但我想我会按照您提到的“并行”将其扔掉。我最近遇到了这个问题,发现问题是我在fork() 下同时使用了两个单独的数据库句柄。当我重构代码以传递单个句柄并取消第二个句柄时,问题就消失了。这记录在docs
  • @GeorgMavridis:这很重要;谢谢。我假设一个 locked 错误就像一个文件系统锁,它可以被任何进程独立应用。听起来 OP 的问题是所有子进程都使用相同的数据库连接,因此彼此锁定。

标签: perl sqlite dbi


【解决方案1】:

更新

请注意以上Georg Mavridis' comment

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

如果您想要真正的并行性,那么您需要与数据库建立多个连接。 SQLite 将对来自不同连接的请求进行排队并为您解决冲突,除非该行为被禁用。



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

  • PrintError — 默认开启 — 如果出现错误,这将导致发出 警告

  • RaiseError——默认关闭——如果出现错误,这将导致进程死亡

  • HandleError — 默认未设置 — 此选项必须设置为 子程序引用,如果出现错误,将调用该子程序

如果您预计不会出现数据库错误,那么最好使用

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

然后,您可以对可能出错的部分代码启用错误处理并尝试修复它

DBI documentation for the RaiseError option 这么说

如果你想暂时关闭 RaiseError (例如在一个可能失败的库函数中),推荐的方式是这样的:

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

这样,RaiseError 选项在右大括号处隐式重新打开,并且在块中,您可以检查 execute 返回的值,表示操作是否成功,errstr,表示操作是否成功给出了持续错误类型的详细信息。然后你可以在 Perl 中编写重试代码来做任何你想做的事情

标准的sleep 调用将暂停一个进程,但粒度为一秒。如果您无法让程序在重试之间等待那么长时间,请查看 Time::HiRes 模块中的 usleep 函数,该函数需要一微秒的倍数

【讨论】:

    猜你喜欢
    • 2011-10-30
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-08-05
    • 2015-05-11
    • 2011-02-14
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多