it-swarm-pt.tech

SQLite3 :: BusyException

Executando um site Rails agora usando SQLite3.

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

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

Qual é a maneira de corrigir isso que seria minimamente invasivo para o 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 rapidamente. No entanto, obviamente não está realmente configurado para acesso simultâneo. Vou migrar para o MySQL amanhã de manhã.

36
Shalmanese

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

 // configure o SQLite para aguardar e tente novamente por até 100 ms se o banco de dados estiver bloqueado 
 sqlite3_busy_timeout (db, 100); 
9
ravenspoint

Você mencionou que este é um Rails site. Rails) permite definir o tempo limite de nova tentativa do SQLite no 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ê no seu log.

Esta é apenas uma solução temporária, no entanto. Se o seu site precisar de simultaneidade verdadeira, será necessário migrar para outro mecanismo de banco de dados.

54
Rifkin Habsburg

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?

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

Esse problema provavelmente está relacionado ao fenômeno do bloqueio de arquivos com compartilhamentos NFS e à falta de simultaneidade do SQLite.

3
ybakos

Apenas para o registro. 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 algumas investigações, encontramos um bug possivelmente relacionado em Rails dev: http://dev.rubyonrails.org/ticket/8811 . E depois de mais algumas investigações, encontrado 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 uma queda no desempenho e agora o aplicativo suporta muitas outras petições sem quebrar (aguarda o tempo limite). Sqlite é agradável!

2
Ignacio Huerta
bundle exec rake db:reset

Funcionou para mim, será redefinida e mostrará a migração pendente.

2
Balaji Radhakrishnan

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

1
meredrica

O Sqlite pode permitir que outros processos esperem até o atual terminar.

Eu uso essa linha para conectar quando sei que posso ter vários processos tentando acessar o Sqlite DB:

conn = sqlite3.connect ('nome do arquivo', isolamento_level = 'exclusivo')

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

Você pode controlar quais tipos de instruções BEGIN o pysqlite executa implicitamente (ou nenhuma) através do parâmetro isolamento_level na chamada connect () ou através da propriedade de isolamento_level das conexões.

1
alfredodeza

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

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

Após 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.
1
Elindor

Se você tiver 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 (compra um [~ # ~] compartilhado [~ # ~] bloqueio)
  2. Leia alguns dados do DB (ainda estamos usando o bloqueio [~ # ~] compartilhado [~ # ~])
  3. Enquanto isso, outro processo inicia uma transação e grava dados (adquirindo o bloqueio [~ # ~] reservado [~ # ~] bloqueio).
  4. Então você tenta escrever, agora você está tentando solicitar o bloqueio [~ # ~] reservado [~ # ~]
  5. O SQLite gera a exceção SQLITE_BUSY imediatamente (independentemente do tempo limite) porque suas leituras anteriores podem não ser mais precisas no momento em que podem receber o [~ # ~] reservado [~ # ~] bloquear.

Uma maneira de corrigir isso é corrigir o active_record adaptador sqlite para adquirir um [~ # ~] reservado [~ # ~] bloquear diretamente no início da transação preenchendo o :immediate opção para o driver. 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-immedediat-in-some-cases-despite-setting-sqlite3_busy_timeout

1
Adrien Jarthon

Fonte: este 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(...)
0
Brian R. Bondy

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?

0
David Medinets

Tente executar o seguinte, isso pode ajudar:

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

De: Ruby: SQLite3 :: BusyException: o banco de dados está bloqueado:

Isso pode esclarecer qualquer transação que suporte o sistema

0
John

Argh - a desgraça da minha existência na última semana. Sqlite3 bloqueia o arquivo db quando qualquer processo grava no banco de dados. IE qualquer consulta do tipo UPDATE/INSERT (também selecione contagem (*) por algum motivo).) No entanto, ele lida com várias leituras.

Então, finalmente fiquei frustrado o suficiente para escrever meu próprio código de bloqueio de thread nas chamadas do banco de dados. Ao garantir que o aplicativo possa ter apenas um thread gravado no banco de dados a qualquer momento, fui capaz de escalar para milhares de threads.

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

0
Voltaire

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

 
 https://github.com/dxj19831029/sqlite3-Ruby[.____.}

Abri uma solicitação de recebimento, sem resposta deles.

De qualquer forma, espera-se alguma exceção ocupada, conforme descrito no próprio sqlite3.

Esteja ciente com esta condição: sqlite busy

 
 A presença de um manipulador ocupado não garante que será invocada quando houver 
 Contenção de bloqueio. Se o SQLite determinar que a chamada do manipulador ocupado pode resultar em um impasse 
, Ele seguirá em frente e retornará SQLITE_BUSY ou SQLITE_IOERR_BLOCKED em vez de 
 Chamar o manipulador ocupado. Considere um cenário em que um processo está mantendo um bloqueio de leitura 
 Que está tentando promover para um bloqueio reservado e um segundo processo está mantendo um bloqueio 
 Reservado que está tentando promover para um bloqueio exclusivo . O primeiro processo não pode continuar 
 Porque está bloqueado pelo segundo e o segundo processo não pode continuar porque está 
 Bloqueado pelo primeiro. Se ambos os processos invocam os manipuladores ocupados, nenhum deles fará 
 Progresso. Portanto, o SQLite retorna SQLITE_BUSY para o primeiro processo, esperando que isso 
 Induza o primeiro processo a liberar seu bloqueio de leitura e permita que o segundo processo 
 Prossiga. 
 

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 begin/commit. ou use bloqueio exclusivo para iniciar/confirmar.

Espero que isto ajude. :)

0
xijing dai

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

0
Anno2001