SQLite3 :: BusyException

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

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

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

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

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

Готов поспорить, ваш хост производственной среды использует NFS для домашнего каталога пользователя приложения, не так ли?

ybakos 19.11.2010 18:41
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
37
1
26 383
16
Перейти к ответу Данный вопрос помечен как решенный

Ответы 16

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

Подходящий инструмент для работы. Пример 1: SQLite отлично подходит для тестирования (его можно запускать в памяти, поэтому он работает быстро). Пример 2: веб-сайт CMS / блога, они обычно имеют небольшой объем, и при кешировании база данных практически не попадает в корзину.

Kris 09.11.2011 19:45

Вы думаете, что человек, задающий этот вопрос, не понимает разницу между SQLite и MySQL

c2h2 19.10.2012 07:53

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

- 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?

Sam 26.03.2010 04:28

Размещение не критично. Где-то после открытия базы данных и до выполнения запроса, который заблокирован. Для удобства помещаю сразу после открытия базы данных.

ravenspoint 26.03.2010 14:17

Вместо этого измените файл конфигурации database.yml, как упомянул Рифкин Габсбург.

docwhat 05.11.2010 21:30

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

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

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

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

Это вызовет для вас sqlite3_busy_timeout в соединении с базой данных.

docwhat 05.11.2010 21:29

Просто не забудьте перезапустить приложение Rails после внесения этого изменения. Не работало, пока я не сделал. :)

Kyle Carlson 12.03.2016 02:35

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?

ybakos 10.08.2011 00:38

Я обнаружил тупик в расширении 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, я ответил на это здесь

Anno2001 31.01.2013 16:38

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

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

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

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