现在使用 SQLite3 运行 Rails 站点。

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

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

有什么方法可以解决这个问题并且对我的代码影响最小?

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

有帮助吗?

解决方案

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

    // set SQLite to wait and retry for up to 100ms if database locked
    sqlite3_busy_timeout( db, 100 );

其他提示

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

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

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

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

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

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

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

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

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

编辑此 ActiveRecord 文件:activerecord-2.3.8/lib/active_record/connection_adapters/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 不错!

bundle exec rake db:reset

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

来源: 这个链接

- 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(...)

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

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

conn = sqlite3.connect('文件名', 隔离级别 = '独占')

根据 Python Sqlite 文档:

您可以通过iSLATION_LEVEL参数到Connect()呼叫或通过连接的iNCLATION_LEVEL属性来控制哪种类型的开始语句Pysqlite隐式执行(或根本没有)。

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

如果你有这个问题但是 增加超时不会改变任何东西, ,您可能会遇到另一个事务并发问题,总结如下:

  1. 开始交易(获取 共享 锁)
  2. 从数据库读取一些数据(我们仍在使用 共享 锁)
  3. 同时,另一个进程启动事务并写入数据(获取 预订的 锁)。
  4. 然后你尝试写,你现在尝试请求 预订的
  5. SQLite 引发 SQLITE_BUSY 异常 立即地 (与您的超时无关)因为您之前的读取在它可以获取时可能不再准确 预订的 锁。

解决此问题的一种方法是修补 active_record sqlite 适配器来获取 预订的 通过填充直接在事务开始时锁定 :immediate 选项给驱动程序。这会稍微降低性能,但至少您的所有事务都会遵守超时并且一个接一个地发生。以下是如何使用以下方法执行此操作 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

大多数答案都是针对 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.

遇到锁时正在访问哪个表?

您有长期运行的交易吗?

您能找出遇到锁时哪些请求仍在处理吗?

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

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

是的,它慢得要命。但它也足够快并且 正确的, ,这是一个很好的财产。

我发现 sqlite3 ruby​​ 扩展出现死锁并在此处修复:尝试一下,看看这是否可以解决您的问题。

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

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

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

意识到 有了这个条件: SQLite 忙

    The presence of a busy handler does not guarantee that it will be invoked when there is 
    lock contention. If SQLite determines that invoking the busy handler could result in a 
    deadlock, it will go ahead and return SQLITE_BUSY or SQLITE_IOERR_BLOCKED instead of 
    invoking the busy handler. Consider a scenario where one process is holding a read lock 
    that it is trying to promote to a reserved lock and a second process is holding a reserved 
    lock that it is trying to promote to an exclusive lock. The first process cannot proceed 
    because it is blocked by the second and the second process cannot proceed because it is 
    blocked by the first. If both processes invoke the busy handlers, neither will make any 
    progress. Therefore, SQLite returns SQLITE_BUSY for the first process, hoping that this 
    will induce the first process to release its read lock and allow the second process to 
    proceed.

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

希望这可以帮助。:)

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

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

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

从: 红宝石:SQLite3::BusyException:数据库被锁定:

这可能会清除阻碍系统的任何交易

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

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top