【问题标题】:SQLite3::BusyExceptionSQLite3::BusyException
【发布时间】:2010-09-09 21:04:53
【问题描述】:

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

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

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

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

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

【问题讨论】:

  • 我打赌你的生产环境主机使用 NFS 作为应用用户的主目录,不是吗?

标签: ruby-on-rails ruby database sqlite


【解决方案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】:

    如果您遇到此问题,但增加超时不会改变任何事情,则您可能在事务方面遇到另一个并发问题,总结如下:

    1. 开始事务(获取 SHARED 锁)
    2. 从 DB 中读取一些数据(我们仍在使用 SHARED 锁)
    3. 同时,另一个进程启动事务并写入数据(获取 RESERVED 锁)。
    4. 然后您尝试写入,您现在正在尝试请求 RESERVED 锁定
    5. 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 中没有设置“只允许一个实例”标志

                    【讨论】:

                    • 这不是对您问题的直接回答,但由于我们经常在 stackoverflows 搜索结果中出现在这里,所以我在这里回答了这个问题
                    【解决方案15】:

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

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

                    发件人:Ruby: SQLite3::BusyException: database is locked:

                    这可以清除任何阻碍系统的事务

                    【讨论】:

                      【解决方案16】:

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

                      【讨论】:

                      • 适合这项工作的工具。示例一:SQLite 非常适合测试(它可以在内存中运行,所以速度很快)。示例二:一个 CMS/博客网站,它们通常是低容量的,并且缓存数据库几乎不会受到影响。
                      • 你认为问这个问题的人不了解 SQLite 与 MySQL 的区别
                      猜你喜欢
                      • 2012-07-02
                      • 2011-08-24
                      • 2012-12-15
                      • 1970-01-01
                      • 2013-11-13
                      • 2017-02-07
                      • 1970-01-01
                      • 2015-06-05
                      • 2020-12-19
                      相关资源
                      最近更新 更多