38

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

大约每 500 个请求左右,我得到一个

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

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

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

4

16 回答 16

57

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

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

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

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

于 2009-04-10T22:15:41.217 回答
9

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

    // 如果数据库锁定,则设置 SQLite 等待并重试最多 100 毫秒
    sqlite3_busy_timeout(分贝,100);
于 2008-09-18T18:21:29.607 回答
3

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

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

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

于 2010-11-19T15:40:49.503 回答
2

只是为了记录。在使用 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 不错!

于 2011-05-23T15:46:39.990 回答
2

如果您有这个问题,但增加超时并没有改变任何东西,您可能还有事务的另一个并发问题,总结如下:

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

解决此问题的一种方法是通过将选项填充到驱动程序来修补active_recordsqlite 适配器以直接在事务开始时获取保留锁。: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

于 2014-10-01T20:35:48.520 回答
2
bundle exec rake db:reset

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

于 2015-02-13T07:09:43.450 回答
1

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

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

conn = sqlite3.connect('文件名',isolation_level = 'exclusive' )

根据 Python Sqlite 文档:

您可以通过 connect() 调用的isolation_level 参数或连接的isolation_level 属性来控制pysqlite 隐式执行(或根本不执行)哪种BEGIN 语句。

于 2009-04-11T03:45:18.117 回答
1

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

于 2011-02-25T00:56:18.000 回答
1

大多数答案是针对 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.
于 2017-12-13T13:11:51.180 回答
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(...)
于 2008-09-17T01:14:50.380 回答
0

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

你有长期交易吗?

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

于 2008-09-17T01:17:57.877 回答
0

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

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

是的,它慢得要命。但它也足够快和正确,这是一个很好的属性。

于 2008-09-17T01:33:32.783 回答
0

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

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

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

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

请注意这种情况:sqlite busy

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

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

希望这可以帮助。:)

于 2012-11-02T04:55:16.707 回答
0

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

于 2013-01-31T12:38:00.070 回答
0

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

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

来自:Ruby:SQLite3::BusyException:数据库被锁定:

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

于 2015-02-13T07:22:08.070 回答
-9

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

于 2008-09-17T01:05:23.350 回答