SQLite3 :: BusyException

35

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

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

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

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

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

  • 0
    какая версия sqlite?
  • 0
    Могу поспорить, что хост вашей производственной среды использует NFS для домашнего каталога пользователя приложения, не так ли?
Теги:
database

16 ответов

7
Лучший ответ

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

    // set SQLite to wait and retry for up to 100ms if database locked
    sqlite3_busy_timeout( db, 100 );
  • 2
    Где вы положили sqlite3_busy_timeout?
  • 0
    Размещение не является критичным. Где-то после открытия базы данных и перед выполнением запроса, который заблокирован. Для удобства размещаю сразу после открытия базы.
Показать ещё 1 комментарий
51

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

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

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

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

  • 1
    Это вызовет sqlite3_busy_timeout для соединения с базой данных для вас.
  • 0
    Просто сделайте перезапуск приложения Rails после внесения этого изменения. Не работал, пока я не сделал. :)
3

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

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

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

2
bundle exec rake db:reset

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

2

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

  • 0
    Спасибо Игнасио. Для AR 3.0.9 просто обратите внимание, что метод немного отличается, но вы все равно измените транзакцию () на транзакцию (: немедленная). Интересно, почему это явно не в кодовой базе AR?
  • 0
    Спас мой день! Спасибо!
1

Если у вас есть эта проблема, но увеличение тайм-аута ничего не меняет, у вас может возникнуть проблема с 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

1

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

1

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

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

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

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

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

1

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

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

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

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

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

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

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

0

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

  • 0
    это не прямой ответ на ваш вопрос, но так как мы часто оказываемся здесь в результате поиска в stackoverflows, я ответил здесь
0

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

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

0

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

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

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

0

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

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

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

-9

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

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

Ещё вопросы

Сообщество Overcoder
Наверх
Меню