SQLite3::BusyException

Posted

技术标签:

【中文标题】SQLite3::BusyException【英文标题】: 【发布时间】:2010-09-09 21:04:53 【问题描述】:

现在使用 SQLite3 运行一个 Rails 站点。

大约每 500 个请求左右,我就会收到一个

ActiveRecord::StatementInvalid (: 数据库被锁定:...

有什么方法可以解决这个问题,对我的代码的侵入性最小?

我目前正在使用 SQLLite,因为您可以将数据库存储在源代码控制中,这使得备份变得自然,并且您可以非常快速地推出更改。但是,它显然不是真正为并发访问设置的。明天早上我会迁移到 mysql

【问题讨论】:

我打赌你的生产环境主机使用 NFS 作为应用用户的主目录,不是吗? 【参考方案1】:

您提到这是一个 Rails 网站。 Rails 允许您在 database.yml 配置文件中设置 SQLite 重试超时:

production:
  adapter: sqlite3
  database: db/mysite_prod.sqlite3
  timeout: 10000

超时值以毫秒为单位。将它增加到 10 或 15 秒应该会减少您在日志中看到的 BusyExceptions 的数量。

不过,这只是一个临时解决方案。如果您的站点需要真正的并发,那么您将不得不迁移到另一个数据库引擎。

【讨论】:

这将为您调用数据库连接上的 sqlite3_busy_timeout。 进行此更改后,请确保重新启动您的 Rails 应用程序。在我做之前没有工作。 :) 在某些情况下似乎可以工作,如果数据库忙,我打开另一个控制台尝试写入,它会挂起一段时间。但是,如果我打开 2 个控制台并在一个循环中写入,其中一个将立即死亡,而不考虑超时。有什么想法吗?【参考方案2】:

默认情况下,如果数据库繁忙并被锁定,sqlite 会立即返回一个阻塞、繁忙的错误。您可以要求它等待并继续尝试一段时间,然后再放弃。这通常可以解决问题,除非您确实有 1000 个线程访问您的数据库,否则我同意 sqlite 是不合适的。

// 如果数据库锁定,则设置 SQLite 等待并重试最多 100 毫秒 sqlite3_busy_timeout(分贝,100);

【讨论】:

你把 sqlite3_busy_timeout 放在哪里? 位置并不重要。在打开数据库之后和执行被阻止的请求之前的某个地方。为方便起见,我打开数据库后立即放置。 改用 Rifkin Habsburg 提到的修改 database.yml 配置文件。【参考方案3】:

所有这些事情都是正确的,但它没有回答问题,这很可能是:为什么我的 Rails 应用程序偶尔会在生产中引发 SQLite3::BusyException?

@Shalmanese:生产托管环境是什么样的?它在共享主机上吗?包含 SQLite 数据库的目录是否位于 NFS 共享上? (可能在共享主机上)。

这个问题可能与 NFS 共享的文件锁定现象和 SQLite 缺乏并发性有关。

【讨论】:

【参考方案4】:

仅作记录。在一个使用 Rails 2.3.8 的应用程序中,我们发现 Rails 忽略了 Rifkin Habsburg 建议的“超时”选项。

经过进一步调查,我们在 Rails 开发中发现了一个可能相关的错误:http://dev.rubyonrails.org/ticket/8811。经过更多调查,我们发现the solution(使用 Rails 2.3.8 测试):

编辑这个 ActiveRecord 文件:activerecord-2.3.8/lib/active_record/connection_adapter/sqlite_adapter.rb

替换这个:

  def begin_db_transaction #:nodoc:
    catch_schema_changes  @connection.transaction 
  end

  def begin_db_transaction #:nodoc:
    catch_schema_changes  @connection.transaction(:immediate) 
  end

仅此而已!我们没有注意到性能下降,现在该应用程序支持更多的请愿而不中断(它等待超时)。 Sqlite 不错!

【讨论】:

谢谢伊格纳西奥。对于 AR 3.0.9,请注意该方法略有不同,但您仍将 transaction() 更改为 transaction(:immediate)。我想知道为什么这在 AR 代码库中没有明确显示?【参考方案5】:

如果您遇到此问题,但增加超时不会改变任何事情,则您可能在事务方面遇到另一个并发问题,总结如下:

    开始事务(获取 SHARED 锁) 从 DB 中读取一些数据(我们仍在使用 SHARED 锁) 同时,另一个进程启动事务并写入数据(获取 RESERVED 锁)。 然后您尝试写入,您现在正在尝试请求 RESERVED 锁定 SQLite立即(与您的超时无关)引发 SQLITE_BUSY 异常,因为您之前的读取可能在它获得 RESERVED 锁时不再准确。李>

解决此问题的一种方法是修补active_record sqlite 适配器,以在事务开始时直接通过将:immediate 选项填充到驱动程序来获取RESERVED 锁。这会稍微降低性能,但至少您的所有事务都会遵守您的超时并一个接一个地发生。以下是如何使用 prepend (Ruby 2.0+) 将其放入初始化程序中:

module SqliteTransactionFix
  def begin_db_transaction
    log('begin immediate transaction', nil)  @connection.transaction(:immediate) 
  end
end

module ActiveRecord
  module ConnectionAdapters
    class SQLiteAdapter < AbstractAdapter
      prepend SqliteTransactionFix
    end
  end
end

在此处阅读更多信息:https://rails.lighthouseapp.com/projects/8994/tickets/5941-sqlite3busyexceptions-are-raised-immediately-in-some-cases-despite-setting-sqlite3_busy_timeout

【讨论】:

这在 2021 年仍然是一个问题 :( - 请问您有在 Rails6 中工作的最新示例吗? @Hackeron 不幸的是,我没有使用过 sqlite。【参考方案6】:
bundle exec rake db:reset

它对我有用,它将重置并显示待处理的迁移。

【讨论】:

【参考方案7】:

Sqlite 可以允许其他进程等到当前进程完成。

当我知道我可能有多个进程试图访问 Sqlite DB 时,我使用此行进行连接:

conn = sqlite3.connect('filename', isolation_level = 'exclusive')

根据 Python Sqlite 文档:

您可以控制哪种类型的 BEGIN 隐式声明 pysqlite 通过 隔离级别参数 connect() 调用,或通过 的隔离级别属性 连接。

【讨论】:

【参考方案8】:

我在使用 rake db:migrate 时遇到了类似的问题。问题是工作目录位于 SMB 共享上。 我通过将文件夹复制到本地计算机来修复它。

【讨论】:

【参考方案9】:

大多数答案是针对 Rails 而不是原始 ruby​​,OP 的问题是针对 rails,这很好。 :)

所以如果任何原始 ruby​​ 用户有这个问题,并且没有使用 yml 配置,我只想把这个解决方案留在这里。

实例化连接后,你可以这样设置:

db = SQLite3::Database.new "#path_to_your_db/your_file.db"
db.busy_timeout=(15000) # in ms, meaning it will retry for 15 seconds before it raises an exception.
#This can be any number you want. Default value is 0.

【讨论】:

【参考方案10】:

来源:this link

- Open the database
db = sqlite3.open("filename")

-- Ten attempts are made to proceed, if the database is locked
function my_busy_handler(attempts_made)
  if attempts_made < 10 then
    return true
  else
    return false
  end
end

-- Set the new busy handler
db:set_busy_handler(my_busy_handler)

-- Use the database
db:exec(...)

【讨论】:

【参考方案11】:

遇到锁时访问的是什么表?

你有长时间运行的交易吗?

你能弄清楚遇到锁时哪些请求仍在处理中吗?

【讨论】:

【参考方案12】:

啊——上周我存在的祸根。当任何进程写入 到数据库时,Sqlite3 会锁定 db 文件。 IE 任何 UPDATE/INSERT 类型的查询(出于某种原因也选择 count(*))。但是,它可以很好地处理多次读取。

所以,我终于很沮丧地围绕数据库调用编写了自己的线程锁定代码。通过确保应用程序在任何时候只能有一个线程写入数据库,我能够扩展到 1000 个线程。

是的,它慢得要命。但它也足够快且正确,这是一个不错的属性。

【讨论】:

【参考方案13】:

我在 sqlite3 ruby​​ 扩展上发现了一个死锁并在这里修复它:试试看这是否能解决你的问题。

https://github.com/dxj19831029/sqlite3-ruby

我打开了一个拉取请求,他们没有回复了。

无论如何,如 sqlite3 本身所述,预计会出现一些繁忙的异常。

注意这种情况:sqlite busy

繁忙的处理程序的存在并不能保证它会在有 锁争用。如果 SQLite 确定调用忙处理程序可能会导致 死锁,它将继续并返回 SQLITE_BUSY 或 SQLITE_IOERR_BLOCKED 而不是 调用繁忙的处理程序。考虑一个进程持有读锁的场景 它正在尝试提升为保留锁,而第二个进程正在持有保留锁 它试图提升为独占锁的锁。第一个过程无法继续 因为它被第二个阻止,第二个进程无法继续,因为它是 被第一个挡住了。如果两个进程都调用繁忙的处理程序,则两者都不会产生任何 进步。因此,SQLite 为第一个进程返回 SQLITE_BUSY,希望这 将诱导第一个进程释放其读锁并允许第二个进程 继续。

如果您满足此条件,则超时不再有效。为避免这种情况,请不要将 select 放在 begin/commit 中。或使用独占锁开始/提交。

希望这会有所帮助。 :)

【讨论】:

【参考方案14】:

这通常是多个进程访问同一个数据库的连续错误,即如果 RubyMine 中没有设置“只允许一个实例”标志

【讨论】:

这不是对您问题的直接回答,但由于我们经常在 ***s 搜索结果中出现在这里,所以我在这里回答了这个问题【参考方案15】:

尝试运行以下命令,可能会有所帮助:

ActiveRecord::Base.connection.execute("BEGIN TRANSACTION; END;") 

发件人:Ruby: SQLite3::BusyException: database is locked:

这可以清除任何阻碍系统的事务

【讨论】:

【参考方案16】:

我相信当交易超时时会发生这种情况。您确实应该使用“真实”数据库。像 Drizzle 或 MySQL 之类的东西。为什么你更喜欢 SQLite 而不是之前的两个选项?

【讨论】:

适合这项工作的工具。示例一:SQLite 非常适合测试(它可以在内存中运行,所以速度很快)。示例二:一个 CMS/博客网站,它们通常是低容量的,并且缓存数据库几乎不会受到影响。 你认为问这个问题的人不了解 SQLite 与 MySQL 的区别

以上是关于SQLite3::BusyException的主要内容,如果未能解决你的问题,请参考以下文章