Запуск сайта rails прямо сейчас с использованием SQLite3.
Примерно раз в 500 запросов я получаю
ActiveRecord :: StatementInvalid (SQLite3 :: BusyException: база данных заблокирована: ...
Как исправить это, чтобы мой код был минимально инвазивным?
В настоящий момент я использую SQLLite, потому что вы можете хранить базу данных в системе управления версиями, что делает резервное копирование естественным, и вы можете очень быстро вносить изменения. Однако он явно не настроен для одновременного доступа. Завтра утром я перейду на MySQL.





Я считаю, что это происходит, когда время транзакции истекает. Вам действительно следует использовать «настоящую» базу данных. Что-то вроде Drizzle или MySQL. Есть ли причина, по которой вы предпочитаете SQLite двум предыдущим вариантам?
Подходящий инструмент для работы. Пример 1: SQLite отлично подходит для тестирования (его можно запускать в памяти, поэтому он работает быстро). Пример 2: веб-сайт CMS / блога, они обычно имеют небольшой объем, и при кешировании база данных практически не попадает в корзину.
Вы думаете, что человек, задающий этот вопрос, не понимает разницу между SQLite и MySQL
Источник: эта ссылка
- 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(...)
К какой таблице осуществляется доступ при обнаружении блокировки?
У вас есть долгосрочные транзакции?
Можете ли вы выяснить, какие запросы все еще обрабатывались, когда была обнаружена блокировка?
Аргх - проклятие моего существования за последнюю неделю. Sqlite3 блокирует файл db, когда любой процесс пишет обращается к базе данных. IE любой запрос типа UPDATE / INSERT (также по какой-то причине выберите count (*)). Однако он отлично справляется с несколькими чтениями.
Итак, я, наконец, достаточно разочаровался, чтобы написать свой собственный код блокировки потока вокруг вызовов базы данных. Убедившись, что приложение может иметь только один поток, записывающий в базу данных в любой момент, я смог масштабироваться до 1000 потоков.
И да, это чертовски медленно. Но он также достаточно быстрый и правильный, что неплохо иметь.
По умолчанию sqlite немедленно возвращается с ошибкой «заблокировано, занято», если база данных занята и заблокирована. Вы можете попросить его подождать и попытаться еще некоторое время, прежде чем сдаться. Обычно это решает проблему, если у вас нет 1000 потоков, обращающихся к вашей базе данных, когда я согласен, что sqlite будет неуместным.
// set SQLite to wait and retry for up to 100ms if database locked
sqlite3_busy_timeout( db, 100 );
Куда поставить sqlite3_busy_timeout?
Размещение не критично. Где-то после открытия базы данных и до выполнения запроса, который заблокирован. Для удобства помещаю сразу после открытия базы данных.
Вместо этого измените файл конфигурации database.yml, как упомянул Рифкин Габсбург.
Вы упомянули, что это сайт Rails. Rails позволяет вам установить время ожидания повторной попытки SQLite в вашем конфигурационном файле database.yml:
production:
adapter: sqlite3
database: db/mysite_prod.sqlite3
timeout: 10000
Значение тайм-аута указывается в миллисекундах. Увеличение его до 10 или 15 секунд должно уменьшить количество BusyExceptions, которые вы видите в своем журнале.
Однако это всего лишь временное решение. Если вашему сайту нужен настоящий параллелизм, вам придется перейти на другой движок db.
Это вызовет для вас sqlite3_busy_timeout в соединении с базой данных.
Просто не забудьте перезапустить приложение Rails после внесения этого изменения. Не работало, пока я не сделал. :)
Sqlite может позволить другим процессам ждать завершения текущего.
Я использую эту строку для подключения, когда знаю, что у меня может быть несколько процессов, пытающихся получить доступ к Sqlite DB:
conn = sqlite3.connect ('имя файла', изоляция_level = 'эксклюзивный')
Согласно документации Python Sqlite:
You can control which kind of BEGIN statements pysqlite implicitly executes (or none at all) via the isolation_level parameter to the connect() call, or via the isolation_level property of connections.
Все это верно, но это не отвечает на вопрос, который, скорее всего, таков: почему мое приложение Rails иногда вызывает SQLite3 :: BusyException в производственной среде?
@Shalmanese: какова среда производственного хостинга? Это на общем хосте? Находится ли каталог, содержащий базу данных sqlite, на общем ресурсе NFS? (Скорее всего, на общем хосте).
Эта проблема, вероятно, связана с феноменом блокировки файлов с общими ресурсами NFS и отсутствием параллелизма в SQLite.
У меня была аналогичная проблема с rake db: migrate. Проблема заключалась в том, что рабочий каталог находился на общем ресурсе SMB. Я исправил это, скопировав папку на свой локальный компьютер.
Только для записи. В одном приложении с 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 хорош!
Спасибо, Игнасио. Для AR 3.0.9 просто обратите внимание, что метод немного отличается, но вы все равно меняете transaction () на transaction (: немедленно). Интересно, почему этого явно нет в базе кода AR?
Я обнаружил тупик в расширении sqlite3 ruby и исправил его здесь: попробуйте и посмотрите, решит ли это проблему ur.
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. или используйте эксклюзивную блокировку для начала / фиксации.
Надеюсь это поможет. :)
это часто является последовательной ошибкой нескольких процессов, обращающихся к одной и той же базе данных, т.е. если флаг «разрешить только один экземпляр» не был установлен в RubyMine
это не прямой ответ на ваш вопрос, но поскольку мы часто оказываемся здесь в результате поиска stackoverflows, я ответил на это здесь
Если у вас есть эта проблема, но увеличение тайм-аута ничего не меняет, у вас может быть другая проблема параллелизма с транзакциями, вот это вкратце:
Один из способов исправить это - исправить адаптер sqlite active_record для получения блокировки ЗАРЕЗЕРВИРОВАННЫЙ непосредственно в начале транзакции, добавив параметр :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-desITE-setting-sqlite3_busy_timeout
bundle exec rake db:reset
Это сработало для меня, он сбросит и покажет ожидающую миграцию.
Попробуйте выполнить следующее, это может помочь:
ActiveRecord::Base.connection.execute("BEGIN TRANSACTION; END;")
От: Ruby: SQLite3 :: BusyException: база данных заблокирована:
Это может устранить любую транзакцию, задерживающую систему.
Большинство ответов предназначены для Rails, а не для сырого рубина, и вопрос OPs IS для rails, что нормально. :)
Поэтому я просто хочу оставить это решение здесь, если у любого пользователя raw 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.
Готов поспорить, ваш хост производственной среды использует NFS для домашнего каталога пользователя приложения, не так ли?