Вопрос

Запуск сайта Rails прямо сейчас с использованием SQLite3.

Примерно раз в 500 запросов я получаю

ActiveRecord::StatementInvalid (SQLite3::BusyException:база данных заблокирована:...

Как это исправить, чтобы мой код был минимально инвазивным?

В данный момент я использую SQLLite, потому что вы можете хранить БД в системе контроля версий, что делает резервное копирование естественным и позволяет очень быстро вносить изменения.Однако очевидно, что он не предназначен для одновременного доступа.Завтра утром я перейду на MySQL.

Это было полезно?

Решение

По умолчанию sqlite немедленно возвращает сообщение об ошибке «заблокировано и занято», если база данных занята и заблокирована.Вы можете попросить его подождать и продолжать попытки некоторое время, прежде чем сдаться.Обычно это решает проблему, если только у вас нет тысяч потоков, обращающихся к вашей базе данных, тогда как я согласен, что sqlite будет неуместен.

    // set SQLite to wait and retry for up to 100ms if database locked
    sqlite3_busy_timeout( db, 100 );

Другие советы

Вы упомянули, что это сайт Rails.Rails позволяет вам установить тайм-аут повтора SQLite в файле конфигурации data.yml:

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

Значение таймаута указывается в миллисекундах.Увеличение этого значения до 10 или 15 секунд должно уменьшить количество BusyExceptions, которые вы видите в своем журнале.

Однако это всего лишь временное решение.Если вашему сайту требуется настоящий параллелизм, вам придется перейти на другой движок базы данных.

Все это правда, но это не отвечает на вопрос, который, скорее всего:почему мое приложение Rails иногда вызывает исключение SQLite3::BusyException в рабочей среде?

@Шалманезе:какова среда производственного хостинга?Это на общем хосте?Находится ли каталог, содержащий базу данных sqlite, на общем ресурсе NFS?(Скорее всего, на общем хосте).

Эта проблема, вероятно, связана с явлением блокировки файлов с помощью общих ресурсов NFS и отсутствием параллелизма в SQLite.

Только для записи.В одном приложении с Rails 2.3.8 мы обнаружили, что Rails игнорировал опцию «таймаута», предложенную Рифкином Габсбургом.

После дополнительного расследования мы обнаружили, возможно, связанную ошибку в разработке 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 хорош!

bundle exec rake db:reset

У меня это сработало, оно перезагрузится и отобразит ожидающую миграцию.

Источник: эта ссылка

- 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(...)

Sqlite может позволить другим процессам ждать завершения текущего.

Я использую эту строку для подключения, когда знаю, что несколько процессов пытаются получить доступ к базе данных Sqlite:

conn = sqlite3.connect('имя файла', уровень изоляции = 'эксклюзивный')

Согласно документации Python Sqlite:

Вы можете контролировать, какие виды операторов Pysqlite неявно выполняют (или вообще нет) через параметр osolation_level к вызову connect () или через свойство подключений stolation_level.

У меня была аналогичная проблема с rake db:migrate.Проблема заключалась в том, что рабочий каталог находился на общем ресурсе SMB.Я исправил это, скопировав папку на свой локальный компьютер.

Если у вас есть эта проблема, но увеличение таймаута ничего не меняет, у вас может возникнуть еще одна проблема с параллелизмом транзакций, вот ее краткое описание:

  1. Начать транзакцию (получает ОБЩИЙ замок)
  2. Прочитайте некоторые данные из БД (мы все еще используем ОБЩИЙ замок)
  3. Тем временем другой процесс запускает транзакцию и записывает данные (получив СДЕРЖАННЫЙ замок).
  4. Затем вы пытаетесь написать, вы сейчас пытаетесь запросить СДЕРЖАННЫЙ замок
  5. SQLite вызывает исключение SQLITE_BUSY немедленно (независимо от вашего тайм-аута), потому что ваши предыдущие чтения могут уже быть неточными к тому времени, когда они смогут получить СДЕРЖАННЫЙ замок.

Один из способов исправить это — пропатчить 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

Подробнее читайте здесь: https://rails.lighthouseapp.com/projects/8994/tickets/5941-sqlite3busyExceptions-are-raising-immediately-in-some-cases-despite-setting-sqlite3_busy_timeout

Большинство ответов относятся к Rails, а не к сырому Ruby, а вопросы ОП относятся к 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.

К какой таблице осуществляется доступ при обнаружении блокировки?

У вас есть длительные транзакции?

Можете ли вы выяснить, какие запросы все еще обрабатывались, когда произошла блокировка?

Ага, проклятие моего существования за последнюю неделю.Sqlite3 блокирует файл базы данных, когда какой-либо процесс пишет в базу данных.IE любой запрос типа UPDATE/INSERT (по какой-то причине также выберите count(*)).Тем не менее, он отлично справляется с несколькими чтениями.

Итак, я, наконец, настолько расстроился, что написал свой собственный код блокировки потоков для вызовов базы данных.Убедившись, что приложение может иметь только один поток, пишущий в базу данных в любой момент, я смог масштабироваться до тысяч потоков.

И да, это чертовски медленно.Но он также достаточно быстрый и правильный, что приятно иметь.

Я нашел тупик в расширении Ruby sqlite3 и исправил его здесь:попробуйте и посмотрите, решит ли это вашу проблему.

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

Я открыл запрос на включение, ответа от них больше нет.

В любом случае ожидается какое-то занятое исключение, как описано в самом sqlite3.

Будьте в курсе с этим условием: sqlite занят

    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.

Если вы соответствуете этому условию, тайм-аут больше не действителен.Чтобы избежать этого, не помещайте select внутри start/commit.или используйте эксклюзивную блокировку для начала/фиксации.

Надеюсь это поможет.:)

часто это последовательная ошибка нескольких процессов, обращающихся к одной и той же базе данных, т.е.если в RubyMine не установлен флаг «разрешить только один экземпляр»

Попробуйте выполнить следующее, может поможет:

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

От: Рубин:SQLite3::BusyException:база данных заблокирована:

Это может очистить любую транзакцию, задерживающую систему.

Я считаю, что это происходит, когда время транзакции истекает.Вам действительно следует использовать «настоящую» базу данных.Что-то вроде Drizzle или MySQL.Есть ли причина, по которой вы предпочитаете SQLite двум предыдущим вариантам?

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top