Подтвердить что ты не робот

SQLite3:: BusyException

Запуск сайта rails прямо сейчас с помощью SQLite3.

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

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

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

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

4b9b3361

Ответ 1

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

    // set SQLite to wait and retry for up to 100ms if database locked
    sqlite3_busy_timeout( db, 100 );

Ответ 2

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

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

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

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

Ответ 3

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

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

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

Ответ 4

Только для записи. В одном приложении с 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 приятно!

Ответ 5

bundle exec rake db:reset

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

Ответ 6

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

- 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(...)

Ответ 7

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

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

conn = sqlite3.connect('filename', isol_level = 'exclusive')

В соответствии с документацией Python Sqlite:

Вы можете контролировать, какой тип BEGIN заявления pysqlite неявно выполняет (или вообще ничего) через параметр изоляции_уровня connect(), или через Свойство isol_level соединения.

Ответ 8

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

Ответ 9

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

  • Начать транзакцию (имеет блокировку SHARED)
  • Прочитайте некоторые данные из БД (мы все еще используем блокировку SHARED)
  • Между тем другой процесс запускает транзакцию и записывает данные (приобретая замок RESERVED).
  • Затем вы пытаетесь написать, теперь вы пытаетесь запросить блокировку RESERVED
  • 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

Ответ 10

Какую таблицу можно получить при обнаружении блокировки?

У вас длительные транзакции?

Можете ли вы выяснить, какие запросы все еще обрабатывались при обнаружении блокировки?

Ответ 11

Арг - проклятие моего существования за последнюю неделю. Sqlite3 блокирует файл db, когда какой-либо процесс записывает в базу данных. IE любой запрос типа UPDATE/INSERT (по какой-либо причине также выберите count (*)). Тем не менее, он обрабатывает несколько чтений просто отлично.

Итак, я, наконец, получил разочарование, чтобы написать свой собственный код блокировки потока вокруг вызовов базы данных. Убедившись, что приложение может иметь только одну запись потока в базу данных в любой момент, я смог масштабировать до 1000 потоков.

И да, это медленно, как ад. Но его также достаточно быстро и правильно, что является хорошим свойством.

Ответ 12

Я нашел тупик в расширении 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. или используйте эксклюзивную блокировку для начала/фиксации.

Надеюсь, это поможет.:)

Ответ 13

это часто является последовательной ошибкой нескольких процессов, обращающихся к одной и той же базе данных, то есть если флаг RubyMine не был установлен в RubyMine

Ответ 15

Большинство ответов относятся к 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.

Ответ 16

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