SQLite3 :: BusyException - PullRequest
       32

SQLite3 :: BusyException

36 голосов
/ 17 сентября 2008

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

Примерно каждые 500 запросов или около того, я получаю

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

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

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

Ответы [ 16 ]

53 голосов
/ 11 апреля 2009

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

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

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

Это только временное решение. Если ваш сайт нуждается в истинном параллелизме, вам придется перейти на другой механизм БД.

9 голосов
/ 18 сентября 2008

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

    // set SQLite to wait and retry for up to 100ms if database locked
    sqlite3_busy_timeout( db, 100 );
3 голосов
/ 19 ноября 2010

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

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

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

2 голосов
/ 13 февраля 2015
bundle exec rake db:reset

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

2 голосов
/ 23 мая 2011

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

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

1 голос
/ 13 декабря 2017

Большинство ответов - для 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.
1 голос
/ 02 октября 2014

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

  1. Начать транзакцию (приобретает SHARED замок)
  2. Чтение некоторых данных из БД (мы все еще используем блокировку SHARED )
  3. Тем временем другой процесс запускает транзакцию и записывает данные (получая блокировку RESERVED ).
  4. Затем вы пытаетесь написать, теперь вы пытаетесь запросить RESERVED lock
  5. SQLite немедленно вызывает исключение SQLITE_BUSY (независимо от вашего времени ожидания), потому что ваши предыдущие чтения могут перестать быть точными к тому времени, когда он может получить блокировку RESERVED .

Одним из способов решения этой проблемы является исправление адаптера active_record sqlite для получения блокировки RESERVED непосредственно в начале транзакции путем добавления опции :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

1 голос
/ 25 февраля 2011

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

1 голос
/ 11 апреля 2009

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

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

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

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

Вы можете контролировать, какой тип НАЧАТЬ заявления pysqlite неявно выполняется (или вообще не выполняется) через Параметр изоляция на уровне вызов connect () или через свойство изоляционного уровня соединения.

1 голос
/ 17 сентября 2008

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

- 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(...)
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...