it-swarm-es.tech

SQLite3 :: BusyException

Ejecutando un sitio Rails ahora mismo usando SQLite3.

Aproximadamente una vez cada 500 solicitudes más o menos, recibo un

ActiveRecord :: StatementInvalid (SQLite3 :: BusyException: la base de datos está bloqueada: ...

¿Cuál es la forma de arreglar esto que sería mínimamente invasivo para mi código?

Estoy usando SQLLite en este momento porque puede almacenar la base de datos en el control de origen, lo que hace que la copia de seguridad sea natural y puede empujar los cambios muy rápidamente. Sin embargo, obviamente no está realmente configurado para acceso concurrente. Migraré a MySQL mañana por la mañana.

36
Shalmanese

Por defecto, sqlite regresa inmediatamente con un error bloqueado y ocupado si la base de datos está ocupada y bloqueada. Puedes pedirle que espere y seguir intentándolo por un tiempo antes de rendirte. Esto generalmente soluciona el problema, a menos que tenga miles de subprocesos accediendo a su base de datos, cuando estoy de acuerdo en que sqlite sería inapropiado.

 // configura SQLite para esperar y volver a intentar hasta 100 ms si la base de datos está bloqueada 
 sqlite3_busy_timeout (db, 100); 
9
ravenspoint

Usted mencionó que este es un Rails sitio. Rails le permite establecer el tiempo de espera de reintento de SQLite en su archivo de configuración database.yml:

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

El valor del tiempo de espera se especifica en milisegundos. Aumentarlo a 10 o 15 segundos debería disminuir la cantidad de BusyExceptions que ve en su registro.

Sin embargo, esta es solo una solución temporal. Si su sitio necesita una verdadera concurrencia, entonces tendrá que migrar a otro motor de base de datos.

54
Rifkin Habsburg

Todas estas cosas son ciertas, pero no responde la pregunta, que es probable: ¿por qué mi aplicación Rails) ocasionalmente genera una SQLite3 :: BusyException en producción?

@Shalmanese: ¿cómo es el entorno de alojamiento de producción? ¿Está en un host compartido? ¿El directorio que contiene la base de datos sqlite está en un recurso compartido NFS? (Probablemente, en un host compartido).

Este problema probablemente tiene que ver con el fenómeno del bloqueo de archivos con recursos compartidos NFS y la falta de concurrencia de SQLite.

3
ybakos

Para que conste. En una aplicación con Rails 2.3.8 descubrimos que Rails ignoraba la opción de "tiempo de espera" sugerida por Rifkin Habsburg.

Después de un poco más de investigación encontramos un error posiblemente relacionado en Rails dev: http://dev.rubyonrails.org/ticket/8811 . Y después de un poco más de investigación, encontrado la solución (probado con Rails 2.3.8):

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

Reemplace esto:

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

con

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

¡Y eso es todo! No hemos notado una caída en el rendimiento y ahora la aplicación admite muchas más peticiones sin interrupciones (espera el tiempo de espera). ¡Sqlite es agradable!

2
Ignacio Huerta
bundle exec rake db:reset

Funcionó para mí, se restablecerá y mostrará la migración pendiente.

2

Tuve un problema similar con rake db: migrate. El problema era que el directorio de trabajo estaba en un recurso compartido SMB. Lo arreglé copiando la carpeta en mi máquina local.

1
meredrica

Sqlite puede permitir que otros procesos esperen hasta que finalice el actual.

Utilizo esta línea para conectarme cuando sé que puedo tener múltiples procesos intentando acceder a Sqlite DB:

conn = sqlite3.connect ('nombre de archivo', aislamiento_nivel = 'exclusivo')

De acuerdo con la Python Sqlite Documentation:

Puede controlar qué tipo de sentencias BEGIN ejecuta pysqlite implícitamente (o ninguna) a través del parámetro aislamiento_nivel a la llamada a connect (), o mediante la propiedad aislamiento_nivel de las conexiones.

1
alfredodeza

La mayoría de las respuestas son para Rails en lugar de Ruby sin procesar, y OPs pregunta IS para Rails, lo cual está bien. :)

Entonces, solo quiero dejar esta solución aquí si cualquier usuario raw Ruby tiene este problema y no está usando una configuración yml).

Después de establecer la conexión, puede configurarla así:

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

Si tiene este problema pero aumentar el tiempo de espera no cambia nada , es posible que tenga otro problema de concurrencia con las transacciones, aquí está en resumen:

  1. Comience una transacción (adquiere un [~ # ~] compartido [~ # ~] bloqueo)
  2. Lea algunos datos de DB (todavía estamos usando el bloqueo [~ # ~] compartido [~ # ~])
  3. Mientras tanto, otro proceso inicia una transacción y escribe datos (adquiriendo el [~ # ~] reservado [~ # ~] bloqueo).
  4. Luego intenta escribir, ahora intenta solicitar el bloqueo [~ # ~] reservado [~ # ~]
  5. SQLite genera la excepción SQLITE_BUSY inmediatamente (independientemente de su tiempo de espera) porque sus lecturas anteriores pueden no ser precisas para cuando pueda obtener el [~ # ~] reservado [~ # ~] bloqueo.

Una forma de solucionar esto es parchar el active_record adaptador sqlite para adquirir a [~ # ~] reservado [~ # ~] bloquear directamente al comienzo de la transacción rellenando el :immediate opción para el conductor. Esto disminuirá un poco el rendimiento, pero al menos todas sus transacciones cumplirán con su tiempo de espera y se producirán una tras otra. Aquí se explica cómo hacer esto usando prepend (Ruby 2.0+) poner esto en un 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

Lea más aquí: https://Rails.lighthouseapp.com/projects/8994/tickets/5941-sqlite3busyexceptions-are-raised-immediately-in-some-cases-despite-setting-sqlite3_busy_timeout

1
Adrien Jarthon

Fuente: este enlace

- 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

¿A qué tabla se accede cuando se encuentra el bloqueo?

¿Tiene transacciones a largo plazo?

¿Puedes averiguar qué solicitudes aún se estaban procesando cuando se encontró el bloqueo?

0
David Medinets

Intente ejecutar lo siguiente, puede ayudar:

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

De: Ruby: SQLite3 :: BusyException: la base de datos está bloqueada:

Esto puede aclarar cualquier transacción que mantenga el sistema

0
John

Argh: la ruina de mi existencia durante la última semana. Sqlite3 bloquea el archivo db cuando cualquier proceso escribe en la base de datos. IE cualquier consulta de tipo ACTUALIZAR/INSERTAR (también seleccione conteo (*) por alguna razón). Sin embargo, maneja múltiples lecturas muy bien.

Entonces, finalmente me frustré lo suficiente como para escribir mi propio código de bloqueo de hilo alrededor de las llamadas a la base de datos. Al asegurarme de que la aplicación solo puede tener un hilo escrito en la base de datos en cualquier momento, pude escalar a miles de hilos.

Y sí, es lento como el infierno. Pero también es lo suficientemente rápido y correcto, que es una buena propiedad para tener.

0
Voltaire

Encontré un punto muerto en la extensión sqlite3 Ruby y corríjalo aquí: pruebe y vea si esto soluciona su problema.

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

Abrí una solicitud de extracción, ya no recibí respuesta de ellos.

De todos modos, se espera alguna excepción de ocupado como se describe en sqlite3.

Tenga en cuenta con esta condición: sqlite ocupado

 
 La presencia de un manejador ocupado no garantiza que se invocará cuando haya contención de bloqueo 
. Si SQLite determina que la invocación del controlador ocupado puede resultar en un punto muerto 
, Continuará y devolverá SQLITE_BUSY o SQLITE_IOERR_BLOCKED en lugar de 
 Invocar al controlador ocupado. Considere un escenario en el que un proceso tiene un bloqueo de lectura 
 Que está tratando de promover a un bloqueo reservado y un segundo proceso tiene un bloqueo reservado 
 Que está tratando de promover a un bloqueo exclusivo . El primer proceso no puede continuar 
 Porque está bloqueado por el segundo y el segundo proceso no puede continuar porque está bloqueado 
 Por el primero. Si ambos procesos invocan a los manejadores ocupados, ninguno hará ningún progreso 
. Por lo tanto, SQLite devuelve SQLITE_BUSY para el primer proceso, esperando que esto 
 Induzca al primer proceso a liberar su bloqueo de lectura y permita que el segundo proceso 
 Continúe. 
 

Si cumple con esta condición, el tiempo de espera ya no es válido. Para evitarlo, no ponga select dentro de begin/commit. o use el bloqueo exclusivo para comenzar/confirmar.

Espero que esto ayude. :)

0
xijing dai

esto es a menudo una falla consecutiva de múltiples procesos que acceden a la misma base de datos, es decir, si el indicador "permitir solo una instancia" no se estableció en RubyMine

0
Anno2001