Pergunta

Executando um site Rails agora usando SQLite3.

Cerca de uma vez a cada 500 solicitações, recebo um

ActiveRecord::StatementInvalid (SQLite3::BusyException:banco de dados está bloqueado:...

Qual é a maneira de corrigir isso que seria minimamente invasivo ao meu código?

Estou usando o SQLLite no momento porque você pode armazenar o banco de dados no controle de origem, o que torna o backup natural e você pode enviar as alterações muito rapidamente.No entanto, obviamente não está configurado para acesso simultâneo.Vou migrar para o MySQL amanhã de manhã.

Foi útil?

Solução

Por padrão, o sqlite retorna imediatamente com um erro bloqueado e ocupado se o banco de dados estiver ocupado e bloqueado.Você pode pedir para esperar e continuar tentando um pouco antes de desistir.Isso geralmente resolve o problema, a menos que você tenha milhares de threads acessando seu banco de dados, quando eu concordo que o sqlite seria inapropriado.

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

Outras dicas

Você mencionou que este é um site Rails.Rails permite que você defina o tempo limite de nova tentativa do SQLite em seu arquivo de configuração database.yml:

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

O valor do tempo limite é especificado em milissegundos.Aumentá-lo para 10 ou 15 segundos deve diminuir o número de BusyExceptions que você vê em seu log.

Esta é apenas uma solução temporária.Se o seu site precisar de simultaneidade verdadeira, você terá que migrar para outro mecanismo de banco de dados.

Todas essas coisas são verdadeiras, mas não respondem à pergunta, o que é provável:por que meu aplicativo Rails ocasionalmente gera uma SQLite3::BusyException em produção?

@Shalmanese:como é o ambiente de hospedagem de produção?Está em um host compartilhado?O diretório que contém o banco de dados sqlite está em um compartilhamento NFS?(Provavelmente, em um host compartilhado).

Esse problema provavelmente tem a ver com o fenômeno de bloqueio de arquivos com compartilhamentos NFS e a falta de simultaneidade do SQLite.

Só para constar.Em uma aplicação com Rails 2.3.8 descobrimos que Rails estava ignorando a opção "timeout" sugerida por Rifkin Habsburg.

Após mais investigações, encontramos um bug possivelmente relacionado no desenvolvedor Rails: http://dev.rubyonrails.org/ticket/8811.E depois de mais alguma investigação, descobrimos a solução (testado com Rails 2.3.8):

Edite este arquivo ActiveRecord:activerecord-2.3.8/lib/active_record/connection_adapters/sqlite_adapter.rb

Substitua isto:

  def begin_db_transaction #:nodoc:
    catch_schema_changes { @connection.transaction }
  end

com

  def begin_db_transaction #:nodoc:
    catch_schema_changes { @connection.transaction(:immediate) }
  end

E isso é tudo!Não notamos queda de desempenho e agora o aplicativo suporta muito mais petições sem quebrar (aguarda o tempo limite).SQLite é legal!

bundle exec rake db:reset

Funcionou para mim, ele irá redefinir e mostrar a migração pendente.

Fonte: esse link

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

O SQLite pode permitir que outros processos esperem até que o atual termine.

Eu uso esta linha para conectar quando sei que posso ter vários processos tentando acessar o banco de dados SQLite:

conn = sqlite3.connect('nome do arquivo', nível de isolamento = 'exclusivo')

De acordo com a documentação do Python SQLite:

Você pode controlar quais tipos de instruções iniciadas são executadas implicitamente (ou nenhuma) através do parâmetro isolation_Level para a chamada Connect () ou através da propriedade ISolation_Level das conexões.

Eu tive um problema semelhante com rake db:migrate.O problema era que o diretório de trabalho estava em um compartilhamento SMB.Eu consertei copiando a pasta para minha máquina local.

Se você tem esse problema, mas aumentar o tempo limite não muda nada, você pode ter outro problema de simultaneidade com transações. Aqui está o resumo:

  1. Iniciar uma transação (adquire um COMPARTILHADO trancar)
  2. Leia alguns dados do DB (ainda estamos usando o COMPARTILHADO trancar)
  3. Enquanto isso, outro processo inicia uma transação e escreve dados (adquirindo o RESERVADO trancar).
  4. Aí você tenta escrever, agora você está tentando solicitar o RESERVADO trancar
  5. SQLite levanta a exceção SQLITE_BUSY imediatamente (independentemente do seu tempo limite), porque suas leituras anteriores podem não ser mais precisas no momento em que for possível obter o RESERVADO trancar.

Uma maneira de corrigir isso é corrigir o active_record adaptador sqlite para adquirir um RESERVADO bloqueie diretamente no início da transação preenchendo o :immediate opção para o motorista.Isso diminuirá um pouco o desempenho, mas pelo menos todas as suas transações respeitarão o tempo limite e ocorrerão uma após a outra.Aqui está como fazer isso usando prepend (Ruby 2.0+) coloque isso em um inicializador:

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

Leia mais aqui: https://rails.lighthouseapp.com/projects/8994/tickets/5941-sqlite3busyexceptions-are-raised-immediately-in-some-cases-despite-setting-sqlite3_busy_timeout

A maioria das respostas é para Rails em vez de Ruby bruto, e os OPs questionam IS para Rails, o que é bom.:)

Então, eu só quero deixar esta solução aqui caso algum usuário Ruby bruto tenha esse problema e não esteja usando uma configuração yml.

Depois de instanciar a conexão, você pode configurá-la assim:

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.

Qual tabela está sendo acessada quando o bloqueio é encontrado?

Você tem transações de longa duração?

Você consegue descobrir quais solicitações ainda estavam sendo processadas quando o bloqueio foi encontrado?

Argh - a ruína da minha existência na última semana.Sqlite3 bloqueia o arquivo db quando qualquer processo escreve para o banco de dados.Ou seja, qualquer consulta do tipo UPDATE/INSERT (selecione também count(*) por algum motivo).No entanto, ele lida perfeitamente com múltiplas leituras.

Então, finalmente fiquei frustrado o suficiente para escrever meu próprio código de bloqueio de thread em torno das chamadas ao banco de dados.Ao garantir que o aplicativo possa ter apenas um thread gravando no banco de dados a qualquer momento, consegui escalar para milhares de threads.

E sim, é lento como o inferno.Mas também é rápido o suficiente e correto, que é uma propriedade agradável de se ter.

Encontrei um impasse na extensão Ruby sqlite3 e corrigi-lo aqui:experimente e veja se isso resolve seu problema.

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

Abri uma solicitação pull, sem mais resposta deles.

De qualquer forma, alguma exceção ocupada é esperada conforme descrito no próprio sqlite3.

Estar ciente com esta condição: sqlite ocupado

    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.

Se você atender a essa condição, o tempo limite não será mais válido.Para evitá-lo, não coloque select dentro de start/commit.ou use bloqueio exclusivo para início/confirmação.

Espero que isto ajude.:)

isso geralmente é uma falha consecutiva de vários processos acessando o mesmo banco de dados, ou seja,se o sinalizador "permitir apenas uma instância" não foi definido no RubyMine

Tente executar o seguinte, pode ajudar:

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

De: Rubi:SQLite3::BusyException:banco de dados está bloqueado:

Isso pode esclarecer qualquer transação que esteja atrapalhando o sistema

Acredito que isso acontece quando uma transação expira.Você realmente deveria estar usando um banco de dados "real".Algo como Drizzle ou MySQL.Alguma razão pela qual você prefere o SQLite às duas opções anteriores?

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top