2008-09-17 16 views
35

Ejecutando un sitio de rails en este momento usando SQLite3.SQLite3 :: BusyException

aproximadamente una vez cada 500 solicitudes más o menos, me siento un

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

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

Estoy utilizando SQLLite en este momento porque puede almacenar el DB en el control de fuente que hace que la copia de seguridad sea natural y puede hacer cambios rápidamente. Sin embargo, obviamente no está realmente configurado para concurrentes acceso. Migraré a MySQL mañana por la mañana.

+0

qué versión de SQLite? –

+0

Apuesto a que el host del entorno de producción usa NFS para el directorio de inicio del usuario de la aplicación, ¿no? – ybakos

Respuesta

7

De forma predeterminada, sqlite devuelve inmediatamente con un error ocupado y bloqueado si la base de datos está ocupada y bloqueada. Puede pedir que espere y seguir intentándolo por un tiempo antes de darse por vencido. 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.

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

¿Dónde coloca el sqlite3_busy_timeout? – Sam

+0

La ubicación no es crítica. En algún lugar después de abrir la base de datos y antes de hacer la solicitud que está bloqueada. Para su comodidad, lo coloco inmediatamente después de abrir la base de datos. – ravenspoint

+11

Modifique el archivo de configuración database.yml como lo menciona Rifkin Habsburg. –

-8

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

+0

La herramienta adecuada para el trabajo. Ejemplo uno: SQLite es ideal para las pruebas (se puede ejecutar en la memoria, por lo que es rápido). Ejemplo dos: un sitio web de CMS/Blog, generalmente son de bajo volumen y con el almacenamiento en caché, la base de datos casi no recibe ningún golpe. – Kris

+0

u piensa que la persona hace esta pregunta no entiende la diferencia entre SQLite vs MySQL – c2h2

1

Fuente: this 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

Qué tabla está siendo visitada cuando se encuentra la cerradura?

¿Tiene transacciones de larga duración?

¿Puede averiguar qué solicitudes se estaban procesando cuando se encontró el bloqueo?

0

Argh - la ruina de mi existencia en la última semana. Sqlite3 bloquea el archivo db cuando cualquier proceso escribe en la base de datos. Es decir, cualquier consulta de tipo UPDATE/INSERT (también selecciona conteo (*) por algún motivo). Sin embargo, maneja múltiples lecturas sin problemas.

Por lo tanto, finalmente me sentí lo suficientemente frustrado como para escribir mi propio código de bloqueo de hilo en las llamadas a la base de datos. Al asegurar que la aplicación solo puede tener un hilo escribiendo en la base de datos en cualquier punto, 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.

53

Mencionaste que este es un sitio de Rails. Raíles 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 tiempo de espera se especifica en milisegundos. Aumentarlo a 10 o 15 segundos debería disminuir el número de BusyExceptions que ve en su registro.

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

+1

Esto llamará a sqlite3_busy_timeout en la conexión de la base de datos por usted. –

+0

Solo asegúrese de reiniciar su aplicación Rails después de hacer este cambio. No funcionó hasta que lo hice. :) –

1

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

puedo utilizar esta línea para conectar cuando sé que puede tener múltiples procesos intentar acceder a la base de datos SQLite:

conn = sqlite3.connect ('nombre', isolation_level = 'exclusivo')

de acuerdo con la documentación de Python SQLite:

puede controlar qué tipo de COMIENZAN declaraciones pysqlite implícitamente ejecuta (o ninguno en absoluto) a través de la parámetro de nivel de aislamiento a la llamada connect(), oa través de propiedad de nivel de aislamiento de conexiones.

3

Todo esto es cierto, pero no responde la pregunta, que es probable: ¿por qué mi aplicación Rails ocasionalmente levanta una SQLite3 :: BusyException en producción?

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

Este problema probablemente tenga que ver con los fenómenos de bloqueo de archivos con recursos compartidos NFS y la falta de concurrencia de SQLite.

1

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.

2

Solo para el registro. 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 the solution (probado con rieles 2.3.8):

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

Reemplazar esta:

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 interrupción (espera el tiempo de espera). Sqlite es bueno!

+0

Gracias Ignacio. Para AR 3.0.9, solo tenga en cuenta que el método es un poco diferente pero aún cambia transacción() a transacción (: inmediato). Me pregunto por qué esto no está explícitamente en la base de código AR. – ybakos

+0

¡Me salvó el día! ¡Gracias! – artemave

0

Encontré un interbloqueo en la extensión sqlite3 ruby ​​y la soluciono aquí: intente con esto y vea si esto soluciona su problema.

 

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

He abierto una solicitud de extracción, sin respuesta de ellos nunca más.

De todos modos, se espera una excepción ocupada como se describe en sqlite3 sí mismo.

Tenga en cuenta con esta condición: sqlite busy

 

    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 esta condición, el tiempo de espera ya no es válida. Para evitarlo, no coloque select inside begin/commit. o use un candado exclusivo para begin/commit.

Espero que esto ayude. bandera :)

0

esto es a menudo un fallo consecutivo de varios procesos con el acceso a misma base de datos, es decir, si el "Permitir sólo una instancia" no se encuentra en RubyMine

+0

esto no es una respuesta directa a su pregunta, pero como a menudo terminamos aquí como resultado de la búsqueda de stackoverflows, respondí esto aquí – Anno2001

1

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

  1. comenzar una transacción (aquires una cerradura COMPARTIDA)
  2. leer algunos datos de DB (seguimos utilizando el COMPARTIDA bloqueo)
  3. Mientras tanto, se inicia otro proceso de una transacción y escribir datos (adquisición de la cerradura RESERVADO).
  4. a continuación, intenta escribir, que ahora está tratando de solicitar el bloqueo RESERVADO
  5. SQLite plantea la excepción SQLITE_BUSY inmediatamente (indenpendently de su tiempo de espera) debido a que sus lecturas anteriores pueden no ser exactos en el momento en que puede obtener el RESERVADO bloqueo.

Una forma de solucionar este problema es que parchear el active_record adaptador de SQLite para adquirir un bloqueo RESERVADO directamente en el comienzo de la operación por el relleno de la opción :immediate 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 después de la otra. Aquí es cómo hacer esto utilizando prepend (Rubí 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 

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

2
bundle exec rake db:reset 

Se trabajó para mí va a restaurar y mostrar la migración en espera .

1

mayoría de las respuestas son para los carriles en lugar de prima Ruby, y la pregunta de OP es para rieles, lo cual está bien. :)

Así que solo quiero dejar esta solución aquí si algún usuario de ruby ​​en bruto tiene este problema, y ​​no está usando una configuración yml.

Después de generar ejemplares de la conexión, se puede establecer la siguiente manera:

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.