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的主要内容,如果未能解决你的问题,请参考以下文章