SQLite3::BusyException
-
09-06-2019 - |
Вопрос
Запуск сайта 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.Я исправил это, скопировав папку на свой локальный компьютер.
Если у вас есть эта проблема, но увеличение таймаута ничего не меняет, у вас может возникнуть еще одна проблема с параллелизмом транзакций, вот ее краткое описание:
- Начать транзакцию (получает ОБЩИЙ замок)
- Прочитайте некоторые данные из БД (мы все еще используем ОБЩИЙ замок)
- Тем временем другой процесс запускает транзакцию и записывает данные (получив СДЕРЖАННЫЙ замок).
- Затем вы пытаетесь написать, вы сейчас пытаетесь запросить СДЕРЖАННЫЙ замок
- 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 двум предыдущим вариантам?