现在使用SQLite3运行Rails站点。
大约每500个请求一次,我得到一个
ActiveRecord :: StatementInvalid(SQLite3 :: BusyException:数据库已锁定:...
有什么办法可以解决这种对我的代码造成最小影响的问题?
目前,我正在使用SQLLite,因为您可以将数据库存储在源代码管理中,这使备份自然而又可以很快地将更改推出。 但是,显然并没有为并发访问设置它。 我明天早上将迁移到MySQL。
您提到这是一个Rails网站。 Rails允许您在database.yml配置文件中设置SQLite重试超时:
1 2 3 4
| production:
adapter: sqlite3
database: db/mysite_prod.sqlite3
timeout: 10000 |
超时值以毫秒为单位指定。将其增加到10或15秒应该会减少您在日志中看到的BusyExceptions的数量。
不过,这只是一个临时解决方案。如果您的站点需要真正的并发,那么您将不得不迁移到另一个数据库引擎。
默认情况下,如果数据库处于忙碌状态且已锁定,则sqlite立即返回一个阻塞的,忙碌的错误。您可以要求它等待并继续尝试一段时间,然后再放弃。通常,这可以解决问题,除非您确实有数千个线程在访问您的数据库,否则我认为sqlite是不合适的。
1 2
| // set SQLite to wait and retry for up to 100ms if database locked
sqlite3_busy_timeout( db, 100 ); |
所有这些事情都是正确的,但是并不能解决问题,这很可能是:为什么我的Rails应用偶尔会在生产中引发SQLite3 :: BusyException?
@Shalmanese:生产托管环境是什么样的?它在共享主机上吗? NFS共享上是否包含sqlite数据库的目录? (就像在共享主机上一样)。
此问题可能与NFS共享的文件锁定现象以及SQLite缺乏并发现象有关。
1
| bundle exec rake db:reset |
它为我工作,它将重置并显示挂起的迁移。
如果您遇到此问题,但增加超时并没有任何改变,则可能是事务还有另一个并发问题,总结如下:
开始交易(获取共享锁)
从数据库读取一些数据(我们仍在使用SHARED锁)
同时,另一个进程开始事务并写入数据(获取RESERVED锁)。
然后尝试编写,现在尝试请求RESERVED锁
SQLite立即引发SQLITE_BUSY异常(与超时无关),因为以前的读取可能在获得RESERVED锁时不再准确。
解决此问题的一种方法是在事务开始时修补active_record sqlite适配器以直接获取RESERVED锁,方法是将:immediate选项填充到驱动程序。这会稍微降低性能,但是至少您的所有事务都可以兑现您的超时,并且一个接一个地发生。这是使用prepend(Ruby 2.0+)将其放入初始化程序的方法:
1 2 3 4 5 6 7 8 9 10 11 12 13
| 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 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
替换为:
1 2 3
| def begin_db_transaction #:nodoc:
catch_schema_changes { @connection.transaction }
end |
与
1 2 3
| def begin_db_transaction #:nodoc:
catch_schema_changes { @connection.transaction(:immediate) }
end |
就这样!我们还没有注意到性能下降,现在该应用程序支持更多请愿书而不会中断(它等待超时)。 Sqlite很好!
我对rake db:migrate有类似的问题。问题是工作目录在SMB共享上。
我通过将文件夹复制到本地计算机来修复它。
大多数答案是针对Rails而不是原始红宝石,OP质疑IS是否适用于Rails,这很好。 :)
因此,如果任何原始红宝石用户遇到此问题,并且不使用yml配置,我只想将此解决方案留在这里。
实例化连接后,可以按以下方式进行设置:
1 2 3
| 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. |
Sqlite可以允许其他进程等到当前进程完成。
当我知道可能有多个进程试图访问Sqlite DB时,我使用这条线进行连接:
conn = sqlite3.connect('文件名',Isolation_level ='独占')
根据Python Sqlite文档:
You can control which kind of BEGIN
statements pysqlite implicitly
executes (or none at all) via the
isolation_level parameter to the
connect() call, or via the
isolation_level property of
connections.
我在sqlite3 ruby??扩展上发现了一个死锁,并在此处进行了修复:试一试,看看是否可以解决您的问题。
1
| https://github.com/dxj19831029/sqlite3-ruby |
我打开了一个拉取请求,不再有任何回应。
无论如何,如sqlite3本身所描述的,可能会遇到一些繁忙的异常。
请注意以下情况:sqlite忙
1 2 3 4 5 6 7 8 9 10 11
| 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. |
如果满足此条件,则超时不再有效。为了避免这种情况,请不要在开始/提交中放入选择。或使用排他锁进行开始/提交。
希望这可以帮助。 :)
这通常是多个进程访问同一数据库的连续错误,即,如果在RubyMine中未设置"只允许一个实例"标志
尝试运行以下命令,可能会有所帮助:
1
| ActiveRecord::Base.connection.execute("BEGIN TRANSACTION; END;") |
来自:Ruby:SQLite3 :: BusyException:数据库被锁定:
这可以清除任何阻碍系统的交易
啊-过去一周我生存的祸根。当任何进程写入数据库时??,Sqlite3都会锁定db文件。 IE进行任何UPDATE / INSERT类型的查询(出于某种原因也请选择count(*))。但是,它可以处理多次读取。
因此,我终于感到沮丧,无法在数据库调用周围编写自己的线程锁定代码。通过确保应用程序在任何时候只能有一个线程写入数据库,我能够扩展到1000个线程。
是的,它慢得要命。但是它也足够快且正确,这是一个不错的属性。
遇到锁定时正在访问哪个表?
您有长期交易吗?
您能找出遇到锁定时仍在处理哪些请求吗?
来源:此链接
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| - 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(...) |
我相信这会在交易超时时发生。您确实应该使用"真实"数据库。像Drizzle或MySQL。为什么您比之前的两个选项更喜欢SQLite?