it-swarm-ru.tech

SQLite3 :: BusyException

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

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

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

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

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

35
Shalmanese

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

 // установить SQLite для ожидания и повторной попытки до 100 мс, если база данных заблокирована 
 sqlite3_busy_timeout (дБ, 100); 
8
ravenspoint

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

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

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

Это всего лишь временное решение. Если ваш сайт нуждается в истинном параллелизме, вам придется перейти на другой механизм БД.

53
Rifkin Habsburg

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

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

Эта проблема, скорее всего, связана с явлением блокировки файлов с общими ресурсами NFS и отсутствием параллелизма в SQLite.

3
ybakos

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

2
Ignacio Huerta
bundle exec rake db:reset

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

2
Balaji Radhakrishnan

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

- 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(...)
1
Brian R. Bondy

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

1
meredrica

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

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

conn = sqlite3.connect ('имя_файла', изоляция_уровень = 'эксклюзив' )

Согласно документации Python Sqlite:

Вы можете контролировать, какой тип НАЧАТЬ Заявления pysqlite неявно выполняется (или вообще не выполняется) через Параметр изоляция на уровне вызов connect () или через свойство изоляционного уровня соединения.

1
alfredodeza

Большинство ответов для Rails, а не для необработанного Ruby, и вопрос OP IS для 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.
1
Elindor

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

  1. Начать транзакцию (получает блокировку SHARED)
  2. Чтение некоторых данных из БД (мы все еще используем блокировку SHARED)
  3. Тем временем другой процесс запускает транзакцию и записывает данные (получая блокировку RESERVED).
  4. Затем вы пытаетесь написать, теперь вы пытаетесь запросить блокировку RESERVED
  5. 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

1
Adrien Jarthon

К какой таблице обращаются при обнаружении блокировки?

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

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

0
David Medinets

Попробуйте выполнить следующее, это может помочь: 

ActiveRecord::Base.connection.execute("BEGIN TRANSACTION; END;") 

От: Ruby: SQLite3 :: BusyException: база данных заблокирована:

Это может очистить любую транзакцию, удерживающую систему

0
John

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

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

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

0
Voltaire

Я нашел тупик на расширении sqlite3 Ruby и исправил его здесь: попробуйте и посмотрите, исправит ли это вашу проблему. 

 https://github.com/dxj19831029/sqlite3-Ruby

Я открыл запрос на удаление, ответа от них больше не было.

В любом случае ожидается некоторое занятое исключение, как описано в самом sqlite3. 

Имейте в виду с этим условием: sqlite занят

 Наличие занятого обработчика не гарантирует, что он будет вызван, когда есть 
 заблокировать раздор. Если SQLite определяет, что вызов занятого обработчика может привести к 
 тупик, он пойдет дальше и вернет SQLITE_BUSY или SQLITE_IOERR_BLOCKED вместо 
 вызывая занятый обработчик. Рассмотрим сценарий, в котором один процесс удерживает блокировку чтения 
 что он пытается перейти к зарезервированной блокировке, а второй процесс удерживает зарезервированную 
 заблокировать, что он пытается повысить до эксклюзивной блокировки. Первый процесс не может продолжаться 
 потому что он заблокирован вторым, и второй процесс не может продолжаться, потому что это 
 заблокирован первым. Если оба процесса вызывают занятые обработчики, ни один из них не создаст 
 прогресс. Поэтому SQLite возвращает SQLITE_BUSY для первого процесса, надеясь, что это 
 заставит первый процесс снять блокировку чтения и разрешить второму процессу 
 продолжить .

Если вы соответствуете этому условию, тайм-аут больше не действителен. Чтобы избежать этого, не помещайте select внутри begin/commit. или используйте эксклюзивную блокировку для начала/фиксации.

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

0
xijing dai

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

0
Anno2001