我同时使用SQLite数据库。主要用于读取数据,这意味着一切都很顺利。但是如果涉及到写入和删除表格,就会在随机时间出现下面的错误(这表明存在竞态条件 - 这在并行运行时是可以预料的):
Error: near line 1: database is locked
现在我知道数据库不会被锁定,需要等待10毫秒再尝试,但我找不到捕获错误的方法。
我该如何捕获这个错误?
请注意上面Georg Mavridis的评论
听起来你的子进程正在共享同一个数据库连接并相互锁定
如果你想要真正的并行处理,那么你需要建立多个数据库连接。SQLite会为你从不同的连接请求中排队并解决冲突,除非该行为被禁用。
您需要设计DBI应用程序的错误处理。在connect
调用中可以指定三个选项:
PrintError
- 默认开启 - 如果出现错误,将发出一个警告
RaiseError
- 默认关闭 - 如果出现错误,将导致进程终止
HandleError
- 默认未设置 - 必须将此选项设置为一个子例程引用,如果出现错误,则将调用该子例程
如果您不希望出现数据库错误,则最好使用
my $dbh = DBI->connect( ..., { PrintError => 0, RaiseError => 1 } )
然后,您可以为代码的某些部分启用错误处理,以防出现问题并尝试修复它
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
函数,它以一微秒的倍数进行计算。
fork()
下同时使用了两个独立的数据库句柄。当我重构代码,改为传递单个句柄并且放弃第二个句柄时,问题就解决了。这在文档中有记录。 - steviebfork()
,在fork()
之后重新打开数据库。根据你的需求,你可能还想调整sqlite_busy_timeout
和sqlite_use_immediate_transaction
(见下文)。”这对我来说意味着,如果你没有多个数据库句柄,问题就会出现。 - Borodin