现在使用 SQLite3 运行一个 Rails 站点。
大约每 500 个请求左右,我得到一个
ActiveRecord::StatementInvalid (SQLite3::BusyException: 数据库被锁定:...
解决这个问题的方法是什么,对我的代码的侵入性最小?
我目前正在使用 SQLLite,因为您可以将数据库存储在源代码控制中,这使得备份变得自然,并且您可以非常快速地推出更改。但是,它显然不是真正为并发访问设置的。明天早上我将迁移到 MySQL。
现在使用 SQLite3 运行一个 Rails 站点。
大约每 500 个请求左右,我得到一个
ActiveRecord::StatementInvalid (SQLite3::BusyException: 数据库被锁定:...
解决这个问题的方法是什么,对我的代码的侵入性最小?
我目前正在使用 SQLLite,因为您可以将数据库存储在源代码控制中,这使得备份变得自然,并且您可以非常快速地推出更改。但是,它显然不是真正为并发访问设置的。明天早上我将迁移到 MySQL。
您提到这是一个 Rails 站点。Rails 允许您在 database.yml 配置文件中设置 SQLite 重试超时:
production:
adapter: sqlite3
database: db/mysite_prod.sqlite3
timeout: 10000
超时值以毫秒为单位指定。将其增加到 10 或 15 秒应该会减少您在日志中看到的 BusyExceptions 的数量。
不过,这只是一个临时解决方案。如果您的站点需要真正的并发,那么您将不得不迁移到另一个数据库引擎。
默认情况下,如果数据库繁忙并被锁定,sqlite 会立即返回一个阻塞、繁忙的错误。您可以要求它等待并继续尝试一段时间,然后再放弃。这通常可以解决问题,除非您确实有 1000 个线程访问您的数据库,否则我同意 sqlite 是不合适的。
// 如果数据库锁定,则设置 SQLite 等待并重试最多 100 毫秒 sqlite3_busy_timeout(分贝,100);
所有这些事情都是正确的,但它并没有回答这个问题,这很可能是:为什么我的 Rails 应用程序偶尔会在生产环境中引发 SQLite3::BusyException?
@Shalmanese:生产托管环境是什么样的?它在共享主机上吗?包含 SQLite 数据库的目录是否位于 NFS 共享上?(可能在共享主机上)。
这个问题可能与带有 NFS 共享的文件锁定现象和 SQLite 缺乏并发性有关。
只是为了记录。在使用 Rails 2.3.8 的一个应用程序中,我们发现 Rails 忽略了 Rifkin Habsburg 建议的“超时”选项。
经过进一步调查,我们在 Rails 开发中发现了一个可能相关的错误: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 不错!
如果您有这个问题,但增加超时并没有改变任何东西,您可能还有事务的另一个并发问题,总结如下:
解决此问题的一种方法是通过将选项填充到驱动程序来修补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
bundle exec rake db:reset
它对我有用,它将重置并显示待处理的迁移。
Sqlite 可以允许其他进程等到当前进程完成。
当我知道我可能有多个进程试图访问 Sqlite DB 时,我使用这条线进行连接:
conn = sqlite3.connect('文件名',isolation_level = 'exclusive' )
根据 Python Sqlite 文档:
您可以通过 connect() 调用的isolation_level 参数或连接的isolation_level 属性来控制pysqlite 隐式执行(或根本不执行)哪种BEGIN 语句。
我对 rake db:migrate 也有类似的问题。问题是工作目录位于 SMB 共享上。我通过将文件夹复制到我的本地计算机来修复它。
大多数答案是针对 Rails 而不是原始 ruby,而 OPs 的问题是针对 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.
来源:此链接
- 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(...)
遇到锁时正在访问哪个表?
你有长期交易吗?
你能弄清楚遇到锁时哪些请求仍在处理中吗?
啊——上周我存在的祸根。当任何进程写入数据库时,Sqlite3 会锁定 db 文件。IE 任何 UPDATE/INSERT 类型的查询(出于某种原因也选择 count(*))。但是,它可以很好地处理多次读取。
所以,我终于很沮丧地围绕数据库调用编写了我自己的线程锁定代码。通过确保应用程序在任何时候只能有一个线程写入数据库,我能够扩展到 1000 个线程。
是的,它慢得要命。但它也足够快和正确,这是一个很好的属性。
我在 sqlite3 ruby 扩展上发现了一个死锁并在这里修复它:试一试,看看这是否解决了你的问题。
https://github.com/dxj19831029/sqlite3-ruby
我打开了一个拉取请求,他们没有回复了。
无论如何,如 sqlite3 本身所述,预计会出现一些繁忙的异常。
请注意这种情况:sqlite busy
繁忙的处理程序的存在并不能保证它会在有 锁争用。如果 SQLite 确定调用忙处理程序可能会导致 死锁,它将继续并返回 SQLITE_BUSY 或 SQLITE_IOERR_BLOCKED 而不是 调用繁忙的处理程序。考虑一个进程持有读锁的场景 它正在尝试提升为保留锁,而第二个进程正在持有保留锁 它试图提升为独占锁的锁。第一个过程无法继续 因为它被第二个阻止,第二个进程无法继续,因为它是 被第一个挡住了。如果两个进程都调用繁忙的处理程序,则两者都不会产生任何 进步。因此,SQLite 为第一个进程返回 SQLITE_BUSY,希望这 将诱导第一个进程释放其读锁并允许第二个进程 继续。
如果满足此条件,则超时不再有效。为避免这种情况,请不要将 select 放在 begin/commit 中。或使用独占锁开始/提交。
希望这可以帮助。:)
这通常是多个进程访问同一个数据库的连续错误,即如果 RubyMine 中没有设置“只允许一个实例”标志
尝试运行以下命令,它可能会有所帮助:
ActiveRecord::Base.connection.execute("BEGIN TRANSACTION; END;")
来自:Ruby:SQLite3::BusyException:数据库被锁定:
这可以清除任何阻碍系统的交易
我相信当交易超时时会发生这种情况。您确实应该使用“真实”数据库。像 Drizzle 或 MySQL 之类的东西。为什么您更喜欢 SQLite 而不是之前的两个选项?