Acceso concurrente SQLite

177

¿SQLite3 maneja de manera segura el acceso concurrente mediante múltiples procesos de lectura / escritura desde el mismo DB? ¿Hay alguna excepción de la plataforma a eso?

anand
fuente
3
Olvidé mencionar la recompensa goall: la mayoría de las respuestas dicen que está bien: "SQLite es lo suficientemente rápido", "SQLite maneja bien la concurrencia", etc. pero, en mi opinión, no responda en detalle / no explique claramente qué sucede si dos operaciones de escritura llegaría exactamente al mismo tiempo (caso teórico muy raro). 1) ¿Activaría un error e interrumpiría el programa? o 2) ¿Esperaría la segunda operación de escritura hasta que termine la primera? o 3) ¿Se descartaría una de las operaciones de escritura (pérdida de datos)? 4) ¿Algo más? Conocer las limitaciones de la escritura concurrente puede ser útil en muchas situaciones.
Basj
77
@Basj En resumen, 2) esperará y volverá a intentarlo varias veces (Configurable), 1) activará un error, SQLITE_BUSY.3) puede registrar una Devolución de llamada para manejar errores de SQLITE_BUSY.
obgnaw

Respuestas:

112

Si la mayoría de esos accesos concurrentes son lecturas (por ejemplo, SELECT), SQLite puede manejarlos muy bien. Pero si comienza a escribir simultáneamente, la contención de bloqueo podría convertirse en un problema. Mucho dependerá de lo rápido que sea su sistema de archivos, ya que el motor SQLite es extremadamente rápido y tiene muchas optimizaciones inteligentes para minimizar la contención. Especialmente SQLite 3.

Para la mayoría de las aplicaciones de escritorio / laptop / tablet / teléfono, SQLite es lo suficientemente rápido ya que no hay suficiente concurrencia. (Firefox usa SQLite ampliamente para marcadores, historial, etc.)

En el caso de las aplicaciones de servidor, alguien dijo hace algún tiempo que una base de datos SQLite podía manejar perfectamente menos de 100K vistas de página por día en escenarios típicos (por ejemplo, blogs, foros), y aún no he visto ninguna evidencia de lo contrario. De hecho, con los discos y procesadores modernos, el 95% de los sitios web y servicios web funcionarían perfectamente con SQLite.

Si desea un acceso de lectura / escritura realmente rápido, use una base de datos SQLite en memoria . La RAM es varios órdenes de magnitud más rápida que el disco.

kijin
fuente
16
OP no pregunta sobre eficiencia y velocidad, sino sobre acceso concurrente. Los servidores web no tienen nada que ver con eso. Lo mismo acerca de la base de datos de memoria.
Jarekczek
1
Tienes razón hasta cierto punto, pero la eficiencia / velocidad juega un papel importante. Los accesos más rápidos significan que el tiempo de espera para los bloqueos es menor, lo que reduce los inconvenientes del rendimiento de concurrencia de SQLite. Particularmente, si tiene pocas y rápidas escrituras, el DB no parecerá tener ningún problema de concurrencia en absoluto para un usuario.
Caboose
1
¿Cómo gestionaría el acceso concurrente a una base de datos sqlite en memoria?
P-Gn
42

Sí, SQLite maneja bien la concurrencia, pero no es lo mejor desde un ángulo de rendimiento. Por lo que puedo decir, no hay excepciones a eso. Los detalles están en el sitio de SQLite: https://www.sqlite.org/lockingv3.html

Esta declaración es de interés: "El módulo de buscapersonas se asegura de que los cambios sucedan de una vez, que todos los cambios ocurran o ninguno de ellos, que dos o más procesos no intenten acceder a la base de datos de manera incompatible al mismo tiempo"

vcsjones
fuente
2
Aquí hay algunos comentarios sobre problemas en diferentes plataformas , a saber, los sistemas de archivos NFS y Windows (aunque podría pertenecer solo a versiones antiguas de Windows ...)
Nate
1
¿Es posible cargar una base de datos SQLite3 en la RAM para ser utilizada por todos los usuarios en PHP? Supongo que no, ya que es procesal
Anon343224user
1
@foxyfennec .. un punto de partida, aunque SQLite puede no ser la base de datos óptima para este caso de uso. sqlite.org/inmemorydb.html
kingPuppy
37

Si lo hace Vamos a descubrir por qué

SQLite es transaccional

Todos los cambios dentro de una sola transacción en SQLite ocurren completamente o no ocurren

Dicha compatibilidad con ACID, así como las lecturas / escrituras simultáneas, se proporcionan de 2 maneras: usando el llamado registro en diario (llamémoslo " forma antigua ") o registro de escritura anticipada (lo llamamos " forma nueva ")

Journaling (Old Way)

En este modo, SQLite utiliza el bloqueo DATABASE-LEVEL . Este es el punto crucial para entender.

Eso significa que cada vez que necesita leer / escribir algo, primero adquiere un bloqueo en el archivo de base de datos COMPLETO . Múltiples lectores pueden coexistir y leer algo en paralelo

Durante la escritura se asegura de adquirir un bloqueo exclusivo y de que ningún otro proceso es lectura / escritura simultánea y, por lo tanto, las escrituras son seguras.

Es por eso que aquí dicen que SQlite implementa transacciones serializables

Nubes

Ya que necesita bloquear una base de datos completa cada vez y todos esperan un proceso de manejo de escritura, la concurrencia sufre y tales escrituras / lecturas concurrentes tienen un rendimiento bastante bajo

Rollbacks / cortes

Antes de escribir algo en el archivo de base de datos, SQLite primero guardaría el fragmento para cambiarlo en un archivo temporal. Si algo falla en el medio de la escritura en el archivo de la base de datos, recogerá este archivo temporal y revertirá los cambios.

Registro de escritura anticipada o WAL (nueva forma)

En este caso, todas las escrituras se agregan a un archivo temporal ( registro de escritura anticipada ) y este archivo se fusiona periódicamente con la base de datos original. Cuando SQLite busca algo, primero verificará este archivo temporal y, si no encuentra nada, continúe con el archivo de la base de datos principal.

Como resultado, los lectores no compiten con los escritores y el rendimiento es mucho mejor en comparación con el Old Way.

Advertencias

SQlite depende en gran medida de la funcionalidad de bloqueo del sistema de archivos subyacente, por lo que debe usarse con precaución, más detalles aquí

También es probable que se encuentre con un error de bloqueo de la base de datos , especialmente en el modo registrado, por lo que su aplicación debe diseñarse teniendo en cuenta este error

fiesta
fuente
34

Nadie parece haber mencionado el modo WAL (Write Ahead Log). Asegúrese de que las transacciones estén organizadas correctamente y con el modo WAL activado, no es necesario mantener la base de datos bloqueada mientras las personas leen mientras se realiza una actualización.

El único problema es que en algún momento el WAL necesita ser reincorporado a la base de datos principal, y lo hace cuando se cierra la última conexión a la base de datos. Con un sitio muy ocupado, es posible que le tome unos segundos cerrar todas las conexiones, pero 100K visitas por día no deberían ser un problema.

akc42
fuente
Interesante, pero funciona solo en una sola máquina, no en escenarios donde se accede a la base de datos en toda la red.
Bobík
Vale la pena mencionar que el tiempo de espera predeterminado para un escritor de espera es de 5 segundos y después de que database is lockedel error será subida por el escritor
Mirhossein
16

En 2019, hay dos nuevas opciones de escritura simultánea que aún no se han lanzado, pero que están disponibles en sucursales separadas.

"PRAGMA journal_mode = wal2"

La ventaja de este modo de diario sobre el modo "wal" normal es que los escritores pueden continuar escribiendo en un archivo wal mientras el otro está marcado.

COMIENCE CONCURRENTE - enlace al documento detallado

La mejora BEGIN CONCURRENT permite que varios escritores procesen transacciones de escritura simultáneamente si la base de datos está en modo "wal" o "wal2", aunque el sistema aún serializa comandos COMMIT.

Cuando se abre una transacción de escritura con "COMENZAR CONCURRENTE", el bloqueo real de la base de datos se aplaza hasta que se ejecuta un COMPROMISO. Esto significa que cualquier cantidad de transacciones iniciadas con BEGIN CONCURRENT puede proceder simultáneamente. El sistema utiliza un bloqueo de nivel de página optimista para evitar la confirmación de transacciones concurrentes en conflicto.

Juntos están presentes en begin-concurrent-wal2 o cada uno en una rama propia separada .

VB
fuente
1
¿Tenemos alguna idea de cuándo esas características llegarán a la versión de lanzamiento? Realmente podrían ser útiles para mí.
Peter Moore
2
Ni idea. Podrías construir fácilmente desde las ramas. Para .NET tengo una biblioteca con interfaz de bajo nivel y WAL2 + inicio simultáneo + FTS5: github.com/Spreads/Spreads.SQLite
VB
Oh, claro gracias. Me pregunto más sobre la estabilidad. La primera categoría de SQLite cuando se trata de sus lanzamientos, pero no sé lo arriesgado que sería usar una rama en el código de producción.
Peter Moore
2
Vea este hilo github.com/Expensify/Bedrock/issues/65 y Bedrock en general. Lo usan en producción y empujan esas begin concurrentcosas.
VB
13

SQLite tiene un bloqueo de lectores y escritores en el nivel de la base de datos. Múltiples conexiones (posiblemente propiedad de diferentes procesos) pueden leer datos de la misma base de datos al mismo tiempo, pero solo uno puede escribir en la base de datos.

SQLite admite un número ilimitado de lectores simultáneos, pero solo permitirá un escritor en cualquier momento. Para muchas situaciones, esto no es un problema. El escritor hace cola. Cada aplicación hace que su base de datos funcione rápidamente y avanza, y ningún bloqueo dura más de unas pocas docenas de milisegundos. Pero hay algunas aplicaciones que requieren más concurrencia, y esas aplicaciones pueden necesitar buscar una solución diferente. - Usos apropiados para SQLite @ SQLite.org

El bloqueo de lectores y escritores permite el procesamiento de transacciones independientes y se implementa utilizando bloqueos exclusivos y compartidos en el nivel de la base de datos.

Se debe obtener un bloqueo exclusivo antes de que una conexión realice una operación de escritura en una base de datos. Después de obtener el bloqueo exclusivo, las operaciones de lectura y escritura de otras conexiones se bloquean hasta que se libera nuevamente el bloqueo.

Detalles de implementación para el caso de escrituras concurrentes

SQLite tiene una tabla de bloqueo que ayuda a bloquear la base de datos lo más tarde posible durante una operación de escritura para garantizar la máxima concurrencia.

El estado inicial es DESBLOQUEADO, y en este estado, la conexión aún no ha accedido a la base de datos. Cuando un proceso se conecta a una base de datos e incluso se inicia una transacción con BEGIN, la conexión aún está en el estado DESBLOQUEADO.

Después del estado DESBLOQUEADO, el siguiente estado es el estado COMPARTIDO. Para poder leer (no escribir) datos de la base de datos, la conexión primero debe ingresar al estado COMPARTIDO, obteniendo un bloqueo COMPARTIDO. Múltiples conexiones pueden obtener y mantener bloqueos COMPARTIDOS al mismo tiempo, por lo que múltiples conexiones pueden leer datos de la misma base de datos al mismo tiempo. Pero mientras solo un bloqueo COMPARTIDO permanezca inédito, ninguna conexión puede completar con éxito una escritura en la base de datos.

Si una conexión quiere escribir en la base de datos, primero debe obtener un bloqueo RESERVADO.

Solo puede estar activo un solo bloqueo RESERVADO a la vez, aunque varios bloqueos COMPARTIDOS pueden coexistir con un solo bloqueo RESERVADO. RESERVADO difiere de PENDIENTE en que se pueden adquirir nuevas cerraduras COMPARTIDAS mientras haya una cerradura RESERVADA. - Bloqueo y concurrencia de archivos en SQLite versión 3 @ SQLite.org

Una vez que una conexión obtiene un bloqueo RESERVADO, puede comenzar a procesar las operaciones de modificación de la base de datos, aunque estas modificaciones solo pueden realizarse en el búfer, en lugar de escribirse realmente en el disco. Las modificaciones realizadas en el contenido de lectura se guardan en el búfer de memoria. Cuando una conexión desea enviar una modificación (o transacción), es necesario actualizar el bloqueo RESERVADO a un bloqueo EXCLUSIVO. Para obtener el bloqueo, primero debe levantar el bloqueo a un bloqueo PENDIENTE.

Un bloqueo PENDIENTE significa que el proceso que contiene el bloqueo desea escribir en la base de datos lo antes posible y solo está esperando que todos los bloqueos COMPARTIDOS actuales se borren para que pueda obtener un bloqueo EXCLUSIVO. No se permiten nuevos bloqueos COMPARTIDOS en la base de datos si un bloqueo PENDIENTE está activo, aunque los bloqueos COMPARTIDOS existentes pueden continuar.

Se necesita un bloqueo EXCLUSIVO para escribir en el archivo de base de datos. Solo se permite un bloqueo EXCLUSIVO en el archivo y ningún otro bloqueo de ningún tipo puede coexistir con un bloqueo EXCLUSIVO. Para maximizar la concurrencia, SQLite trabaja para minimizar la cantidad de tiempo que se mantienen los bloqueos EXCLUSIVOS. - Bloqueo y concurrencia de archivos en SQLite versión 3 @ SQLite.org

¡Entonces podría decir que SQLite maneja de manera segura el acceso concurrente mediante múltiples procesos que escriben en el mismo DB simplemente porque no lo admite! Obtendrá SQLITE_BUSYo SQLITE_LOCKEDpara el segundo escritor cuando llegue a la limitación de reintento.

obgnaw
fuente
Gracias. Un ejemplo de código con 2 escritores sería genial para entender cómo funciona.
Basj
1
En resumen, @Basj, sqlite tiene un bloqueo de lectura-escritura en el archivo de la base de datos. Es lo mismo que escribir un archivo concurrentemente. Y con WAL, todavía no puede escribir simultáneamente, pero WAL puede acelerar la escritura, y la lectura y escritura pueden ser concurrentes. Y WAL introduce un nuevo bloqueo como WAL_READ_LOCK, WAL_WRITE_LOCK.
obgnaw
2
¿Podría usar una cola y hacer que varios hilos alimenten la cola y solo un hilo que escriba en la base de datos utilizando las declaraciones SQL en la cola? Algo como esto
Gabriel el
7

Este hilo es antiguo, pero creo que sería bueno compartir el resultado de mis pruebas realizadas en sqlite: ejecuté 2 instancias del programa python (diferentes procesos, el mismo programa) ejecutando instrucciones SELECT y UPDATE comandos sql dentro de la transacción con EXCLUSIVE lock and timeout establecido en 10 segundos para obtener un bloqueo, y el resultado fue frustrante. Cada instancia lo hizo en un bucle de 10000 pasos:

  • conectarse a db con bloqueo exclusivo
  • seleccione en una fila para leer el contador
  • actualizar la fila con un nuevo valor igual al contador incrementado en 1
  • conexión cercana a db

Incluso si a sqlite se le otorgara un bloqueo exclusivo en la transacción, el número total de ciclos realmente ejecutados no fue igual a 20 000, sino menos (número total de iteraciones en un solo contador contado para ambos procesos). El programa Python casi no arrojó ninguna excepción (solo una vez durante la selección para 20 ejecuciones). La revisión de sqlite en el momento de la prueba fue 3.6.20 y Python v3.3 CentOS 6.5. En mi opinión, es mejor encontrar un producto más confiable para este tipo de trabajo o restringir las escrituras a sqlite a un único proceso / hilo único.

asf las
fuente
55
Parece que necesita decir algunas palabras mágicas para bloquear Python, como se explica aquí: stackoverflow.com/a/12848059/1048959 Esto a pesar del hecho de que la documentación de Python sqlite lo lleva a creer que with cones suficiente.
Dan Stahlke