Acceso concurrente SQLite

¿SQLite3 maneja con seguridad el acceso concurrente mediante múltiples procesos de lectura / escritura desde el mismo DB? ¿Hay alguna excepción de plataforma para eso?

Si la mayoría de esos accesos concurrentes son leídos (por ejemplo, SELECT), SQLite puede manejarlos muy bien. Pero si comienza a escribir al mismo tiempo, la contención del locking podría convertirse en un problema. Mucho dependerá de qué tan rápido 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 / tableta / teléfono, SQLite es lo suficientemente rápido ya que no hay suficiente concurrencia. (Firefox usa SQLite extensivamente para marcadores, historial, etc.)

En el caso de las aplicaciones de servidor, alguien dijo hace algún tiempo que cualquier base de datos SQLite podía manejar perfectamente cualquier página de SQL de menos de 100K 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 bien con SQLite.

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

Sí, SQLite maneja bien la concurrencia, pero no es la 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 afirmación es de interés: “El módulo de buscapersonas asegura que los cambios suceden de una vez, que todos los cambios ocurren o ninguno de ellos ocurre, que dos o más procesos no intentan acceder a la base de datos de manera incompatible al mismo tiempo”

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 cosas mientras se lleva a cabo 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, puede tardar unos segundos para que todas las conexiones estén cerradas, pero 100.000 visitas al día no deberían ser un problema.

Sí 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 en absoluto

Dicho soporte de ACID, así como lecturas / escrituras concurrentes se proporcionan de 2 maneras: utilizando el llamado registro en diario (llamémoslo ” antiguo “) o registro por escritura anticipada (vamos a llamarlo ” nuevo modo “)

Diario (Old Way)

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

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

Durante la escritura, se asegura de que se obtenga un locking exclusivo y que ningún otro proceso esté leyendo / escribiendo simultáneamente y, por lo tanto, las escrituras son seguras.

Es por eso que dicen que SQlite implementa transacciones serializables

Nubes

Como necesita bloquear una base de datos completa cada vez y todo el mundo espera un proceso que maneja la concurrencia de escritura sufre y dichas escrituras / lecturas simultáneas tienen un rendimiento bastante bajo.

Revertir / interrupciones

Antes de escribir algo en el archivo de la base de datos, SQLite primero guardará 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ía este archivo temporal y revertiría los cambios desde él.

Registro por Escritura Adelantado o WAL (New Way)

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

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

Advertencias

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

También es probable que se tope con la base de datos que está bloqueada , especialmente en el modo diario, por lo que su aplicación debe diseñarse teniendo en cuenta este error.

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

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

Incluso si sqlite concedió el locking exclusivo en la transacción, el número total de ciclos realmente ejecutados no fue igual a 20 000, sino menos (el número total de iteraciones sobre contador individual se contabilizó para ambos procesos). El progtwig Python casi no lanzó ninguna excepción (solo una vez durante seleccionar 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.

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. Escritor cola. Cada aplicación hace que su base de datos trabaje rápidamente y se mueva, y ningún locking 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.

Está claro en el DOC.

En el procesamiento de transacciones, SQLite implementa el procesamiento de transacciones independiente a través de los lockings exclusivos y compartidos en el nivel de la base de datos. Y es por esto que múltiples procesos pueden leer datos de la misma base de datos al mismo tiempo, pero solo uno puede escribir en la base de datos.

Se debe obtener un locking exclusivo antes de que un proceso o subproceso desee realizar una operación de escritura en una base de datos. Después de obtener el locking exclusivo, no se volverán a producir otras operaciones de lectura o escritura.

Implementar detalles, por ejemplo, dos escritos:

SQLite tiene una tabla de locking para ayudar a las diferentes bases de datos de escritura a bloquear en el último momento 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 una base de datos está conectada a una base de datos e incluso se ha iniciado una transacción con BEGIN, la conexión sigue en estado “DESBLOQUEADO”.

El siguiente estado del estado desbloqueado es un estado COMPARTIDO. Para poder leer (no escribir) datos de la base de datos, la conexión debe primero ingresar al estado COMPARTIDO, es decir, primero para obtener un locking COMPARTIDO. Múltiples conexiones pueden obtener y mantener lockings COMPARTIDOS al mismo tiempo, es decir, múltiples conexiones pueden leer datos de la misma base de datos al mismo tiempo. Pero incluso si no se ha liberado un solo locking COMPARTIDO, no permite que ninguna conexión escriba una base de datos.

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

Solo un locking RESERVADO puede estar activo a la vez, aunque varios lockings COMPARTIDOS pueden coexistir con un único locking RESERVADO. RESERVED difiere de PENDING en que los nuevos lockings SHARED pueden adquirirse mientras haya un locking RESERVED.

Una vez que una conexión obtiene un locking 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 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 locking reservado a un locking exclusivo. Para obtener el candado, primero debe levantar el candado a un candado pendiente.

Un locking PENDIENTE significa que el proceso que contiene el locking desea escribir en la base de datos lo antes posible y solo espera que todos los lockings COMPARTIDOS actuales se eliminen para poder obtener un locking EXCLUSIVO. No se permiten nuevos lockings COMPARTIDOS contra la base de datos si un locking PENDIENTE está activo, aunque se permite que los lockings COMPARTIDOS existentes continúen.

Se necesita un locking EXCLUSIVO para escribir en el archivo de la base de datos. Solo se permite un locking EXCLUSIVO en el archivo y no se permite que otros lockings de ningún tipo coexistan con un locking EXCLUSIVO. Para maximizar la concurrencia, SQLite trabaja para minimizar la cantidad de tiempo que se mantienen los lockings EXCLUSIVOS.

Por lo tanto, SQLite maneja con seguridad el acceso simultáneo mediante múltiples procesos que escriben desde el mismo archivo base porque no lo admite. Obtendrá SQLITE_BUSY o SQLITE_LOCKED para el segundo escritor cuando SQLITE_LOCKED la limitación de rebash.