Pregunta

Ejecutando un sitio Rails ahora mismo usando SQLite3.

Aproximadamente una vez cada 500 solicitudes aproximadamente, recibo un

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

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

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

¿Fue útil?

Solución

De forma predeterminada, sqlite regresa inmediatamente con un error de ocupado y bloqueado si la base de datos está ocupada y bloqueada.Puedes pedirle que espere y seguir intentándolo un rato 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, sqlite sería inapropiado.

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

Otros consejos

Mencionaste que este es un sitio Rails.Rails le permite configurar 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 sólo una solución temporal.Si su sitio necesita una verdadera concurrencia, tendrá que migrar a otro motor de base de datos.

Todas estas cosas son ciertas, pero no responde a la pregunta, que probablemente sea:¿Por qué mi aplicación Rails ocasionalmente genera una excepción SQLite3::BusyException en producción?

@Salmanase:¿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 tenga que ver con el fenómeno del bloqueo de archivos con recursos compartidos NFS y la falta de simultaneidad de SQLite.

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

Después de investigar un poco más, 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 encontramos 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 interrumpirse (espera el tiempo de espera).¡Sqlite es bueno!

bundle exec rake db:reset

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

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

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 la base de datos Sqlite:

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

Según la documentación de Python Sqlite:

Puede controlar qué tipo de declaraciones iniciales se ejecuta implícitamente (o ninguno) a través del parámetro aislamiento_level a la llamada Connect (), o a través de la propiedad de las conexiones aisladas_level.

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 a mi máquina local.

Si tienes este problema pero aumentar el tiempo de espera no cambia nada, es posible que tengas otro problema de concurrencia con las transacciones, aquí lo tienes resumido:

  1. Iniciar una transacción (adquiere un COMPARTIDO cerrar con llave)
  2. Lea algunos datos de DB (todavía estamos usando el COMPARTIDO cerrar con llave)
  3. Mientras tanto, otro proceso inicia una transacción y escribe datos (adquiriendo el RESERVADO cerrar con llave).
  4. Luego intentas escribir, ahora estás intentando solicitar el RESERVADO cerrar
  5. SQLite genera la excepción SQLITE_BUSY inmediatamente (independientemente de su tiempo de espera) porque es posible que sus lecturas anteriores ya no sean precisas cuando pueda obtener el RESERVADO cerrar con llave.

Una forma de solucionar este problema es parchear el active_record Adaptador sqlite para adquirir un RESERVADO bloquear directamente al comienzo de la transacción rellenando el :immediate opción al conductor.Esto disminuirá un poco el rendimiento, pero al menos todas sus transacciones respetarán su tiempo de espera y se producirán una tras otra.Aquí se explica cómo hacer esto usando prepend (Ruby 2.0+) ponga 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

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

La mayoría de las respuestas son para Rails en lugar de Raw Ruby, y los OP cuestionan IS para Rails, lo cual está bien.:)

Así que solo quiero dejar esta solución aquí en caso de que algún usuario de Ruby sin formato tenga este problema y no esté usando una configuración yml.

Después de crear una instancia de la conexión, puedes 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.

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

¿Tiene transacciones de larga duración?

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

Argh: la pesadilla de mi existencia durante la última semana.Sqlite3 bloquea el archivo db cuando hay algún proceso escribe a la base de datos.Es decir, cualquier consulta de tipo ACTUALIZAR/INSERTAR (también seleccione recuento (*) por algún motivo).Sin embargo, maneja bien múltiples lecturas.

Entonces, finalmente me frustré lo suficiente como para escribir mi propio código de bloqueo de subprocesos en torno a las llamadas a la base de datos.Al asegurarme de que la aplicación solo pueda tener un subproceso escribiendo en la base de datos en cualquier momento, pude escalar a miles de subprocesos.

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

Encontré un punto muerto en la extensión ruby ​​sqlite3 y lo solucioné aquí:Pruébelo y vea si esto soluciona su problema.

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

Abrí una solicitud de extracción y ya no hubo respuesta de ellos.

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

ser consciente con esta condición: 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.

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

Espero que esto ayude.:)

Esto suele ser una falla consecutiva de múltiples procesos que acceden a la misma base de datos, es decir.si el indicador "permitir sólo una instancia" no estaba configurado en RubyMine

Intente ejecutar lo siguiente, puede ayudar:

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

De: Rubí:SQLite3::Excepción ocupada:la base de datos está bloqueada:

Esto puede aclarar cualquier transacción que esté retrasando el sistema.

Creo que esto sucede cuando se agota el tiempo de espera de una transacción.Realmente deberías utilizar una base de datos "real".Algo así como Drizzle o MySQL.¿Alguna razón por la que prefiere SQLite a las dos opciones anteriores?

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top