Запуск сайта rails прямо сейчас с помощью SQLite3.
Примерно каждые 500 запросов или около того я получаю
ActiveRecord:: StatementInvalid (SQLite3:: BusyException: база данных заблокирована:...
Какой способ исправить это, что будет минимально инвазивным для моего кода?
Я использую SQLLite на данный момент, потому что вы можете хранить БД в исходном элементе управления, что делает резервное копирование естественным, и вы можете быстро вывести изменения. Однако он явно не настроен для одновременного доступа. Завтра я перейду на MySQL.
По умолчанию sqlite немедленно возвращается с заблокированной, занятой ошибкой, если база данных занята и заблокирована. Вы можете попросить его подождать и продолжить некоторое время, прежде чем сдаться. Обычно это исправляет проблему, если у вас нет 1000 потоков, обращающихся к вашему db, когда я соглашусь, что sqlite будет неуместным.
// set SQLite to wait and retry for up to 100ms if database locked sqlite3_busy_timeout( db, 100 );
Вы упомянули, что это сайт Rails. Rails позволяет установить тайм-аут повторной попытки SQLite в файле конфигурации database.yml:
production:
adapter: sqlite3
database: db/mysite_prod.sqlite3
timeout: 10000
Значение таймаута указано в миллисекундах. Увеличение его до 10 или 15 секунд должно уменьшить количество BusyExceptions, которые вы видите в своем журнале.
Это лишь временное решение. Если ваш сайт нуждается в истинном concurrency, вам придется перейти на другой движок db.
Все эти вещи верны, но это не отвечает на вопрос, что вполне вероятно: почему приложение Rails иногда вызывает исключение SQLite3:: BusyException?
@Shalmanese: что такое производственная хостинговая среда? Это на общем хосте? Является ли каталог, содержащий базу данных sqlite для общего ресурса NFS? (Вероятно, на общем хосте).
Эта проблема, вероятно, связана с явлениями блокировки файлов w/NFS-частями и отсутствием SQLite concurrency.
bundle exec rake db:reset
Это сработало для меня, это будет reset и покажет ожидающую миграцию.
Только для записи. В одном приложении с 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 приятно!
Если у вас есть эта проблема, но увеличение тайм-аута ничего не меняет, у вас может возникнуть проблема с concurrency с транзакциями, вот она вкратце:
Одним из способов исправить это является исправление адаптера 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
У меня была аналогичная проблема с rake db: migrate. Проблема заключалась в том, что рабочий каталог находился на SMB-ресурсе. Я исправил его, скопировав папку на свою локальную машину.
Sqlite может позволить другим процессам дождаться завершения текущего.
Я использую эту строку для подключения, когда знаю, что у меня может быть несколько процессов, пытающихся получить доступ к базе данных Sqlite:
conn = sqlite3.connect('filename', isol_level = 'exclusive')
В соответствии с документацией Python Sqlite:
Вы можете контролировать, какой тип BEGIN заявления pysqlite неявно выполняет (или вообще ничего) через параметр изоляции_уровня connect(), или через Свойство isol_level соединения.
Источник: эта ссылка
- 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(...)
Большинство ответов относятся к Rails, а не к рубину raw, а OP - к IS для рельсов, и это нормально.:)
Итак, я просто хочу оставить это решение здесь, если у какого-то необработанного пользователя 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.
Попробуйте выполнить следующее, это может помочь:
ActiveRecord::Base.connection.execute("BEGIN TRANSACTION; END;")
От: Ruby: SQLite3:: BusyException: база данных заблокирована:
Это может устранить любую транзакцию, поддерживающую систему
это часто является последовательной ошибкой нескольких процессов, обращающихся к одной и той же базе данных, то есть если флаг RubyMine не был установлен в RubyMine
Я нашел тупик в расширении 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 внутри begin/commit. или используйте эксклюзивную блокировку для начала/фиксации.
Надеюсь, это поможет.:)
Какую таблицу можно получить при обнаружении блокировки?
У вас длительные транзакции?
Можете ли вы выяснить, какие запросы все еще обрабатывались при обнаружении блокировки?
Арг - проклятие моего существования за последнюю неделю. Sqlite3 блокирует файл db, когда какой-либо процесс записывает в базу данных. IE любой запрос типа UPDATE/INSERT (по какой-либо причине также выберите count (*)). Тем не менее, он обрабатывает несколько чтений просто отлично.
Итак, я, наконец, получил разочарование, чтобы написать свой собственный код блокировки потока вокруг вызовов базы данных. Убедившись, что приложение может иметь только одну запись потока в базу данных в любой момент, я смог масштабировать до 1000 потоков.
И да, это медленно, как ад. Но его также достаточно быстро и правильно, что является хорошим свойством.
Я считаю, что это происходит, когда транзакция истекает. Вы действительно должны использовать "настоящую" базу данных. Что-то вроде "Дождь" или MySQL. Любая причина, по которой вы предпочитаете SQLite по двум предыдущим параметрам?