1. Descripción general2. Cómo funciona WAL 2.1. Puntos de control 2.2. Simultaneidad 2.3. Consideraciones de rendimiento 3. Activación y configuración del modo WAL3.1. Punto de control automático3.2. Puntos de control iniciados por la aplicación 3.3. Persistencia del modo WAL 4. El archivo WAL 5. Bases de datos de solo lectura 6. Evitar archivos WAL excesivamente grandes 7. Implementación de memoria compartida para el índice WAL 8. Uso de WAL sin memoria compartida 9. A veces, las consultas devuelven SQLITE_BUSY en modo WAL 10. Compatibilidad al revés

El método predeterminado por el cual SQLite implementa el compromiso atómico y la reversión es un diario de reversión. Empezando con versión 3.7.0 (2010-07-21), está disponible una nueva opción de “Registro de escritura anticipada” (en lo sucesivo, “WAL”).

Existen ventajas y desventajas al usar WAL en lugar de un diario de reversión. Las ventajas incluyen:

  1. WAL es significativamente más rápido en la mayoría de los escenarios.
  2. WAL proporciona más simultaneidad ya que los lectores no bloquean a los escritores y un escritor no bloquea a los lectores. La lectura y la escritura pueden realizarse al mismo tiempo.
  3. Las operaciones de E / S de disco tienden a ser más secuenciales con WAL.
  4. WAL usa muchas menos operaciones fsync () y por lo tanto es menos vulnerable a problemas en sistemas donde la llamada al sistema fsync () está rota.

Pero también hay desventajas:

  1. WAL normalmente requiere que VFS admita primitivas de memoria compartida. (Excepción: WAL sin memoria compartida) Los VFS integrados de Unix y Windows admiten esto, pero es posible que los VFS de extensión de terceros para sistemas operativos personalizados no lo sean.
  2. Todos los procesos que utilizan una base de datos deben estar en la misma computadora host; WAL no funciona en un sistema de archivos de red.
  3. Las transacciones que involucran cambios en múltiples bases de datos ATTACHed son atómicas para cada base de datos individual, pero no son atómicas en todas las bases de datos como un conjunto.
  4. No es posible cambiar page_size después de ingresar al modo WAL, ya sea en una base de datos vacía o usando VACUUM o restaurando desde una copia de seguridad usando la API de copia de seguridad. Debe estar en modo de diario de reversión para cambiar el tamaño de la página.
  5. No es posible abrir bases de datos WAL de solo lectura. El proceso de apertura debe tener privilegios de escritura para “-shm“archivo de memoria compartida wal-index asociado con la base de datos, si ese archivo existe, o bien acceso de escritura en el directorio que contiene el archivo de base de datos si el”-shm” el archivo no existe. Empezando con versión 3.22.0 (2018-01-22), se puede abrir un archivo de base de datos en modo WAL de solo lectura si el -shm y -wal los archivos ya existen o esos archivos se pueden crear o la base de datos es inmutable.
  6. WAL puede ser un poco más lento (quizás un 1% o un 2% más lento) que el enfoque tradicional de rollback-journal en aplicaciones que principalmente leen y pocas veces escriben.
  7. Hay un cuasi-persistente adicional “-wal“archivo y”-shm“archivo de memoria compartida asociado con cada base de datos, lo que puede hacer que SQLite sea menos atractivo para su uso como formato de archivo de aplicación.
  8. Existe la operación adicional de puntos de control que, aunque automática por defecto, sigue siendo algo que los desarrolladores de aplicaciones deben tener en cuenta.
  9. WAL funciona mejor con transacciones más pequeñas. WAL no funciona bien para transacciones muy grandes. Para transacciones de más de aproximadamente 100 megabytes, los modos de diario de reversión tradicionales probablemente serán más rápidos. Para transacciones de más de un gigabyte, el modo WAL puede fallar con un error de E / S o de disco lleno. Se recomienda utilizar uno de los modos de diario de reversión para transacciones de más de unas pocas docenas de megabytes. Empezando con versión 3.11.0 (2016-02-15), el modo WAL funciona tan eficientemente con transacciones grandes como lo hace el modo de reversión.

El diario de reversión tradicional funciona escribiendo una copia del contenido de la base de datos original sin cambios en un archivo de diario de reversión independiente y luego escribiendo los cambios directamente en el archivo de base de datos. En caso de un bloqueo o ROLLBACK, el contenido original del diario de reversión se reproduce en el archivo de base de datos para revertir el archivo de base de datos a su estado original. El COMMIT se produce cuando se elimina el diario de reversión.

El enfoque WAL invierte esto. El contenido original se conserva en el archivo de la base de datos y los cambios se agregan a un archivo WAL separado. Un COMMIT ocurre cuando un registro especial que indica un compromiso se agrega al WAL. Por lo tanto, un COMMIT puede ocurrir sin tener que escribir en la base de datos original, lo que permite a los lectores continuar operando desde la base de datos original inalterada mientras los cambios se confirman simultáneamente en la WAL. Se pueden agregar varias transacciones al final de un solo archivo WAL.

2.1. Checkpointing

Por supuesto, uno quiere transferir eventualmente todas las transacciones que se adjuntan en el archivo WAL a la base de datos original. Mover las transacciones del archivo WAL de nuevo a la base de datos se denomina “control“.

Otra forma de pensar en la diferencia entre el registro de reversión y escritura anticipada es que en el enfoque de reversión del diario, hay dos operaciones primitivas, lectura y escritura, mientras que con un registro de escritura anticipada ahora hay tres operaciones primitivas: lectura, escritura. y puntos de control.

De forma predeterminada, SQLite realiza un punto de control automáticamente cuando el archivo WAL alcanza un tamaño de umbral de 1000 páginas. (La opción de tiempo de compilación SQLITE_DEFAULT_WAL_AUTOCHECKPOINT puede usarse para especificar un valor predeterminado diferente). Las aplicaciones que usan WAL no tienen que hacer nada para que ocurran estos puntos de control. Pero si lo desean, las aplicaciones pueden ajustar el umbral del punto de control automático. O pueden desactivar los puntos de control automáticos y ejecutar puntos de control durante los momentos de inactividad o en un hilo o proceso separado.

2.2. Concurrencia

Cuando una operación de lectura comienza en una base de datos en modo WAL, primero recuerda la ubicación del último registro de confirmación válido en WAL. Llame a este punto la “marca final”. Debido a que WAL puede crecer y agregar nuevos registros de confirmación mientras varios lectores se conectan a la base de datos, cada lector puede tener potencialmente su propia marca final. Pero para cualquier lector en particular, la marca de finalización no se modifica durante la duración de la transacción, lo que garantiza que una sola transacción de lectura solo vea el contenido de la base de datos tal como existía en un solo momento.

Cuando un lector necesita una página de contenido, primero verifica el WAL para ver si esa página aparece allí y, si es así, extrae la última copia de la página que aparece en el WAL antes de la marca final del lector. Si no existe una copia de la página en el WAL antes de la marca final del lector, entonces la página se lee del archivo de base de datos original. Los lectores pueden existir en procesos separados, por lo que para evitar obligar a cada lector a escanear todo el WAL en busca de páginas (el archivo WAL puede crecer a varios megabytes, dependiendo de la frecuencia con la que se ejecutan los puntos de control), una estructura de datos llamada “índice wal” se mantiene en la memoria compartida, lo que ayuda a los lectores a localizar páginas en el WAL rápidamente y con un mínimo de E / S. El índice wal mejora enormemente el rendimiento de los lectores, pero el uso de memoria compartida significa que todos los lectores deben existir en la misma máquina. Esta es la razón por la que la implementación del registro de escritura anticipada no funcionará en un sistema de archivos de red.

Los escritores simplemente agregan contenido nuevo al final del archivo WAL. Debido a que los escritores no hacen nada que interfiera con las acciones de los lectores, los escritores y los lectores pueden ejecutar al mismo tiempo. Sin embargo, dado que solo hay un archivo WAL, solo puede haber un escritor a la vez.

Una operación de punto de control toma el contenido del archivo WAL y lo transfiere de nuevo al archivo de base de datos original. Un punto de control puede ejecutarse al mismo tiempo que los lectores, sin embargo, el punto de control debe detenerse cuando llega a una página en el WAL que sobrepasa la marca final de cualquier lector actual. El punto de control tiene que detenerse en ese punto porque, de lo contrario, podría sobrescribir parte del archivo de base de datos que el lector está utilizando activamente. El punto de control recuerda (en el índice wal) qué tan lejos llegó y reanudará la transferencia de contenido desde el WAL a la base de datos desde donde lo dejó en la siguiente invocación.

Por lo tanto, una transacción de lectura de larga duración puede evitar que un puntero de control avance. Pero presumiblemente, cada transacción de lectura terminará eventualmente y el puntero de control podrá continuar.

Siempre que ocurre una operación de escritura, el escritor verifica cuánto progreso ha hecho el puntero de control, y si todo el WAL se ha transferido a la base de datos y se ha sincronizado y si ningún lector está haciendo uso del WAL, entonces el escritor rebobinará el WAL de nuevo a el comienzo y comience a poner nuevas transacciones al comienzo del WAL. Este mecanismo evita que un archivo WAL crezca sin límite.

2.3. Consideraciones de rendimiento

Las transacciones de escritura son muy rápidas, ya que solo implican escribir el contenido una vez (frente a dos veces para las transacciones del diario de reversión) y porque las escrituras son todas secuenciales. Además, no es necesario sincronizar el contenido con el disco, siempre que la aplicación esté dispuesta a sacrificar la durabilidad después de una pérdida de energía o un reinicio completo. (Los escritores sincronizan el WAL en cada confirmación de transacción si PRAGMA synchronous se establece en FULL, pero omiten esta sincronización si PRAGMA synchronous se establece en NORMAL).

Por otro lado, el rendimiento de lectura se deteriora a medida que el archivo WAL aumenta de tamaño, ya que cada lector debe verificar el contenido del archivo WAL y el tiempo necesario para verificar el archivo WAL es proporcional al tamaño del archivo WAL. El índice wal ayuda a encontrar contenido en el archivo WAL mucho más rápido, pero el rendimiento aún disminuye al aumentar el tamaño del archivo WAL. Por lo tanto, para mantener un buen rendimiento de lectura, es importante reducir el tamaño del archivo WAL ejecutando puntos de control a intervalos regulares.

Los puntos de verificación requieren operaciones de sincronización para evitar la posibilidad de que la base de datos se dañe después de una pérdida de energía o un reinicio completo. El WAL debe sincronizarse con el almacenamiento persistente antes de mover el contenido del WAL a la base de datos y el archivo de la base de datos debe sincronizarse antes de restablecer el WAL. Checkpoint también requiere más búsqueda. El puntero hace un esfuerzo para realizar tantas escrituras de página secuenciales en la base de datos como sea posible (las páginas se transfieren de WAL a la base de datos en orden ascendente) pero incluso entonces, normalmente habrá muchas operaciones de búsqueda intercaladas entre las escrituras de página. Estos factores se combinan para hacer que los puntos de control sean más lentos que las transacciones de escritura.

La estrategia predeterminada es permitir que las transacciones de escritura sucesivas aumenten el WAL hasta que el WAL tenga un tamaño de aproximadamente 1000 páginas, luego ejecutar una operación de punto de control para cada COMMIT subsiguiente hasta que el WAL se restablezca para que sea menor de 1000 páginas. De forma predeterminada, el punto de control será ejecutado automáticamente por el mismo hilo que hace COMMIT que empuja al WAL por encima de su límite de tamaño. Esto tiene el efecto de hacer que la mayoría de las operaciones COMMIT sean muy rápidas, pero que COMMIT ocasional (aquellas que activan un punto de control) sea mucho más lento. Si ese efecto no es deseable, la aplicación puede deshabilitar los puntos de control automáticos y ejecutar los puntos de control periódicos en un hilo o proceso separado. (Los enlaces a comandos e interfaces para lograr esto se muestran a continuación).

Tenga en cuenta que con PRAGMA sincrónico establecido en NORMAL, el punto de control es la única operación para emitir una barrera de E / S o una operación de sincronización (fsync () en Unix o FlushFileBuffers () en Windows). Por lo tanto, si una aplicación ejecuta un punto de control en un subproceso o proceso separado, el subproceso o proceso principal que realiza consultas y actualizaciones de la base de datos nunca se bloqueará en una operación de sincronización. Esto ayuda a evitar el bloqueo en aplicaciones que se ejecutan en una unidad de disco ocupada. La desventaja de esta configuración es que las transacciones ya no son duraderas y pueden retroceder después de un corte de energía o un restablecimiento completo.

Observe también que existe una compensación entre el rendimiento medio de lectura y el rendimiento medio de escritura. Para maximizar el rendimiento de lectura, se desea mantener el WAL lo más pequeño posible y, por lo tanto, ejecutar puntos de control con frecuencia, quizás con la misma frecuencia que en cada COMMIT. Para maximizar el rendimiento de escritura, se desea amortizar el costo de cada punto de control en tantas escrituras como sea posible, lo que significa que se desea ejecutar puntos de control con poca frecuencia y dejar que la WAL crezca lo más posible antes de cada punto de control. Por lo tanto, la decisión de la frecuencia con la que se ejecutarán los puntos de control puede variar de una aplicación a otra dependiendo de los requisitos relativos de rendimiento de lectura y escritura de la aplicación. La estrategia predeterminada es ejecutar un punto de control una vez que WAL alcanza las 1000 páginas y esta estrategia parece funcionar bien en aplicaciones de prueba en estaciones de trabajo, pero otras estrategias pueden funcionar mejor en diferentes plataformas o para diferentes cargas de trabajo.

Una conexión de base de datos SQLite tiene como valor predeterminado journal_mode = DELETE. Para convertir al modo WAL, use el siguiente pragma:

PRAGMA journal_mode=WAL;

El pragma journal_mode devuelve una cadena que es el nuevo modo de diario. Si tiene éxito, el pragma devolverá la cadena “wal“. Si no se pudo completar la conversión a WAL (por ejemplo, si el VFS no admite las primitivas de memoria compartida necesarias), el modo de registro por diario no se modificará y la cadena devuelta por la primitiva será el modo de registro por diario anterior (por ejemplo “delete“).

3.1. Punto de control automático

De forma predeterminada, SQLite marcará automáticamente un punto de control cada vez que se produzca un COMMIT que haga que el archivo WAL tenga un tamaño de 1000 páginas o más, o cuando se cierre la última conexión a la base de datos en un archivo de base de datos. La configuración predeterminada está diseñada para funcionar bien para la mayoría de las aplicaciones. Pero los programas que quieren más control pueden forzar un punto de control usando el pragma wal_checkpoint o llamando a la interfaz C sqlite3_wal_checkpoint (). El umbral del punto de control automático se puede cambiar o el punto de control automático se puede deshabilitar por completo usando el pragma wal_autocheckpoint o llamando a la interfaz C sqlite3_wal_autocheckpoint (). Un programa también puede usar sqlite3_wal_hook () para registrar una devolución de llamada que se invocará cada vez que cualquier transacción se comprometa con WAL. Esta devolución de llamada puede invocar sqlite3_wal_checkpoint () o sqlite3_wal_checkpoint_v2 () según los criterios que considere apropiados. (El mecanismo de punto de control automático se implementa como un envoltorio simple alrededor de sqlite3_wal_hook ()).

3.2. Puntos de control iniciados por la aplicación

Una aplicación puede iniciar un punto de control utilizando cualquier conexión de base de datos de escritura en la base de datos simplemente invocando sqlite3_wal_checkpoint () o sqlite3_wal_checkpoint_v2 (). Hay tres subtipos de puntos de control que varían en su agresividad: PASIVO, COMPLETO y REINICIO. El estilo de punto de control predeterminado es PASIVO, que hace todo el trabajo que puede sin interferir con otras conexiones de base de datos, y que podría no ejecutarse hasta el final si hay lectores o escritores simultáneos. Todos los puntos de control iniciados por sqlite3_wal_checkpoint () y por el mecanismo de punto de control automático son PASIVOS. Los puntos de control FULL y RESTART se esfuerzan más para ejecutar el punto de control hasta su finalización y solo pueden iniciarse mediante una llamada a sqlite3_wal_checkpoint_v2 (). Consulte la documentación sqlite3_wal_checkpoint_v2 () para obtener información adicional sobre los puntos de control FULL y RESET.

3.3. Persistencia del modo WAL

A diferencia de los otros modos de registro en diario, PRAGMA journal_mode = WAL es persistente. Si un proceso establece el modo WAL, luego cierra y vuelve a abrir la base de datos, la base de datos volverá al modo WAL. Por el contrario, si un proceso establece (por ejemplo) PRAGMA journal_mode = TRUNCATE y luego cierra y vuelve a abrir, la base de datos volverá a aparecer en el modo de reversión predeterminado de DELETE en lugar de la configuración anterior de TRUNCATE.

La persistencia del modo WAL significa que las aplicaciones se pueden convertir para usar SQLite en modo WAL sin realizar ningún cambio en la aplicación en sí. Uno simplemente tiene que correr “PRAGMA journal_mode=WAL;“en los archivos de la base de datos utilizando el shell de línea de comandos u otra utilidad, luego reinicie la aplicación.

El modo de diario WAL se establecerá en todas las conexiones al mismo archivo de base de datos si está configurado en cualquier conexión.

Mientras una conexión de base de datos está abierta en una base de datos en modo WAL, SQLite mantiene un archivo de diario adicional llamado “Registro de escritura anticipada” o “Archivo WAL”. El nombre de este archivo en el disco suele ser el nombre del archivo de la base de datos con un “-wal“, aunque se pueden aplicar diferentes reglas de nomenclatura si SQLite se compila con SQLITE_ENABLE_8_3_NAMES.

El archivo WAL existe mientras cualquier conexión de base de datos tenga la base de datos abierta. Por lo general, el archivo WAL se elimina automáticamente cuando se cierra la última conexión a la base de datos. Sin embargo, si el último proceso para tener la base de datos abierta sale sin cerrar limpiamente la conexión de la base de datos, o si se usa el control SQLITE_FCNTL_PERSIST_WALfile, entonces el archivo WAL podría retenerse en el disco después de que se hayan cerrado todas las conexiones a la base de datos. El archivo WAL es parte del estado persistente de la base de datos y debe mantenerse con la base de datos si la base de datos se copia o se mueve. Si un archivo de base de datos se separa de su archivo WAL, es posible que se pierdan las transacciones que se comprometieron previamente con la base de datos o que el archivo de la base de datos se corrompa. La única forma segura de eliminar un archivo WAL es abrir el archivo de la base de datos usando una de las interfaces sqlite3_open () y luego cerrar inmediatamente la base de datos usando sqlite3_close ().

El formato de archivo WAL está definido con precisión y es multiplataforma.

Las versiones anteriores de SQLite no podían leer una base de datos en modo WAL que fuera de solo lectura. En otras palabras, se requería acceso de escritura para leer una base de datos en modo WAL. Esta restricción se relajó a partir de SQLite versión 3.22.0 (22 de enero de 2018).

En las versiones más recientes de SQLite, una base de datos en modo WAL en medios de solo lectura, o una base de datos en modo WAL que carece de permiso de escritura, aún se puede leer siempre que se cumplan una o más de las siguientes condiciones:

  1. los -shm y -wal los archivos ya existen y son legibles
  2. Hay permiso de escritura en el directorio que contiene la base de datos para que el -shm y -wal se pueden crear archivos.
  3. La conexión a la base de datos se abre mediante el parámetro de consulta inmutable.

Aunque es posible abrir una base de datos en modo WAL de solo lectura, es una buena práctica convertir a PRAGMA journal_mode = DELETE antes de grabar una imagen de base de datos SQLite en un medio de solo lectura.

En casos normales, se agrega contenido nuevo al archivo WAL hasta que el archivo WAL acumula aproximadamente 1000 páginas (y por lo tanto tiene un tamaño de aproximadamente 4 MB), momento en el que se ejecuta automáticamente un punto de control y se recicla el archivo WAL. El punto de control normalmente no trunca el archivo WAL (a menos que se establezca el pragma journal_size_limit). En cambio, simplemente hace que SQLite comience a sobrescribir el archivo WAL desde el principio. Esto se hace porque normalmente es más rápido sobrescribir un archivo existente que agregarlo. Cuando se cierra la última conexión a una base de datos, esa conexión realiza un último punto de control y luego elimina la WAL y su archivo de memoria compartida asociado, para limpiar el disco.

Entonces, en la gran mayoría de los casos, las aplicaciones no necesitan preocuparse en absoluto por el archivo WAL. SQLite se encargará de ello automáticamente. Pero es posible llevar SQLite a un estado en el que el archivo WAL crecerá sin límites, lo que provocará un uso excesivo de espacio en disco y velocidades de consulta lentas. Las siguientes viñetas enumeran algunas de las formas en que esto puede suceder y cómo evitarlas.

  • Desactivación del mecanismo de control automático. En su configuración predeterminada, SQLite controlará el archivo WAL al final de cualquier transacción cuando el archivo WAL tenga más de 1000 páginas. Sin embargo, existen opciones de tiempo de compilación y tiempo de ejecución que pueden deshabilitar o aplazar este punto de control automático. Si una aplicación desactiva el punto de control automático, no hay nada que impida que el archivo WAL crezca excesivamente.

  • Hambruna en el puesto de control. Un punto de control solo puede ejecutarse hasta su finalización y restablecer el archivo WAL, si no hay otras conexiones de base de datos que utilicen el archivo WAL. Si otra conexión tiene una transacción de lectura abierta, entonces el punto de control no puede restablecer el archivo WAL porque hacerlo podría eliminar el contenido debajo del lector. El punto de control hará todo el trabajo que pueda sin molestar al lector, pero no puede ejecutarse hasta su finalización. El punto de control se iniciará nuevamente donde se detuvo después de la siguiente transacción de escritura. Esto se repite hasta que se pueda completar algún punto de control.

    Sin embargo, si una base de datos tiene muchos lectores superpuestos simultáneos y siempre hay al menos un lector activo, no se podrán completar puntos de control y, por lo tanto, el archivo WAL crecerá sin límites.

    Este escenario se puede evitar asegurándose de que haya “brechas de lectura”: momentos en los que ningún proceso está leyendo de la base de datos y que se intentan puntos de control durante esos momentos. En aplicaciones con muchos lectores simultáneos, también se podría considerar ejecutar puntos de control manuales con la opción SQLITE_CHECKPOINT_RESTART o SQLITE_CHECKPOINT_TRUNCATE que asegurará que el punto de control se ejecute hasta su finalización antes de regresar. La desventaja de usar SQLITE_CHECKPOINT_RESTART y SQLITE_CHECKPOINT_TRUNCATE es que los lectores pueden bloquearse mientras se ejecuta el punto de control.

  • Transacciones de escritura muy grandes. Un punto de control solo se puede completar cuando no se están ejecutando otras transacciones, lo que significa que el archivo WAL no se puede restablecer en medio de una transacción de escritura. Entonces un un cambio grande en una base de datos grande puede resultar en un archivo WAL grande. El archivo WAL se marcará una vez que se complete la transacción de escritura (asumiendo que no hay otros lectores bloqueándolo) pero mientras tanto, el archivo puede crecer mucho.

    A partir de SQLite versión 3.11.0 (2016-02-15), el archivo WAL para una sola transacción debe ser proporcional en tamaño a la transacción en sí. Las páginas que se cambian por la transacción solo deben escribirse en el archivo WAL una vez. Sin embargo, con versiones anteriores de SQLite, la misma página podría escribirse en el archivo WAL varias veces si la transacción crece más que el caché de la página.

El índice wal se implementa usando un archivo ordinario que está mapeado por robustez. Las implementaciones tempranas (previas al lanzamiento) del modo WAL almacenaban el índice wal en memoria compartida volátil, como archivos creados en / dev / shm en Linux o / tmp en otros sistemas Unix. El problema con ese enfoque es que los procesos con un directorio raíz diferente (cambiado a través de chroot) verá diferentes archivos y, por lo tanto, usará diferentes áreas de memoria compartida, lo que provocará daños en la base de datos. Otros métodos para crear bloques de memoria compartida sin nombre no son portátiles en las distintas versiones de Unix. Y no pudimos encontrar ningún método para crear bloques de memoria compartida sin nombre en Windows. La única forma que hemos encontrado para garantizar que todos los procesos que acceden al mismo archivo de base de datos usen la misma memoria compartida es crear la memoria compartida mapeando un archivo en el mismo directorio que la propia base de datos.

El uso de un archivo de disco normal para proporcionar memoria compartida tiene la desventaja de que en realidad podría realizar E / S de disco innecesarias escribiendo la memoria compartida en el disco. Sin embargo, los desarrolladores no creen que esto sea una preocupación importante, ya que el índice wal rara vez supera los 32 KiB y nunca se sincroniza. Además, el archivo de respaldo de wal-index se elimina cuando se desconecta la última conexión a la base de datos, lo que a menudo evita que se produzca una E / S de disco real.

Las aplicaciones especializadas para las que la implementación predeterminada de la memoria compartida es inaceptable pueden diseñar métodos alternativos a través de un VFS personalizado. Por ejemplo, si se sabe que una base de datos en particular solo será accedida por subprocesos dentro de un solo proceso, el índice wal se puede implementar usando memoria de pila en lugar de la verdadera memoria compartida.

Comenzando en SQLite versión 3.7.4 (2010-12-07), las bases de datos WAL se pueden crear, leer y escribir incluso si la memoria compartida no está disponible, siempre y cuando el lock_mode esté establecido en EXCLUSIVE antes del primer intento de acceso. En otras palabras, un proceso puede interactuar con una base de datos WAL sin usar memoria compartida si se garantiza que ese proceso será el único proceso que acceda a la base de datos. Esta característica permite que las bases de datos WAL sean creadas, leídas y escritas por VFS heredados que carecen de los métodos de memoria compartida “versión 2” xShmMap, xShmLock, xShmBarrier y xShmUnmap en el objeto sqlite3_io_methods.

Si el modo de bloqueo EXCLUSIVO se establece antes del primer acceso a la base de datos en modo WAL, entonces SQLite nunca intenta llamar a ninguno de los métodos de memoria compartida y, por lo tanto, nunca se crea ningún índice de memoria compartida. En ese caso, la conexión a la base de datos permanece en modo EXCLUSIVO siempre que el modo de diario sea WAL; intenta cambiar el modo de bloqueo usando “PRAGMA locking_mode=NORMAL;“no son operativas. La única forma de salir del modo de bloqueo EXCLUSIVO es salir primero del modo de diario WAL.

Si el modo de bloqueo NORMAL está en efecto para el primer acceso a la base de datos en modo WAL, se crea el índice wal de memoria compartida. Esto significa que el VFS subyacente debe admitir la memoria compartida “versión 2”. Si el VFS no admite métodos de memoria compartida, el intento de abrir una base de datos que ya está en modo WAL, o el intento de convertir una base de datos en modo WAL, fallará. Siempre que exactamente una conexión utilice un índice de memoria compartida, el modo de bloqueo se puede cambiar libremente entre NORMAL y EXCLUSIVO. Solo cuando se omite el índice wal de memoria compartida, cuando el modo de bloqueo es EXCLUSIVO antes del primer acceso a la base de datos en modo WAL, el modo de bloqueo se bloquea en EXCLUSIVO.

La segunda ventaja del modo WAL es que los escritores no bloquean a los lectores y los lectores no bloquean a los escritores. Este es principalmente verdadero. Pero hay algunos casos oscuros en los que una consulta en una base de datos en modo WAL puede devolver SQLITE_BUSY, por lo que las aplicaciones deben estar preparadas para esa casualidad.

Los casos en los que una consulta en una base de datos en modo WAL puede devolver SQLITE_BUSY incluyen los siguientes:

  • Si otra conexión de base de datos tiene el modo de base de datos abierto en modo de bloqueo exclusivo, todas las consultas contra la base de datos devolverán SQLITE_BUSY. Tanto Chrome como Firefox abren sus archivos de base de datos en modo de bloqueo exclusivo, por lo que los intentos de leer las bases de datos de Chrome o Firefox mientras las aplicaciones se están ejecutando se encontrarán con este problema, por ejemplo.

  • Cuando se cierra la última conexión a una base de datos en particular, esa conexión adquirirá un bloqueo exclusivo durante un breve período de tiempo mientras limpia los archivos de memoria compartida y WAL. Si una segunda base de datos intenta abrir y consultar la base de datos mientras la primera conexión aún se encuentra en medio de su proceso de limpieza, la segunda conexión puede obtener un error SQLITE_BUSY.

  • Si la última conexión a una base de datos falla, la primera conexión nueva para abrir la base de datos iniciará un proceso de recuperación. Se mantiene un bloqueo exclusivo durante la recuperación. Entonces, si una tercera conexión de base de datos intenta entrar y consultar mientras la segunda conexión se está recuperando, la tercera conexión obtendrá un error SQLITE_BUSY.

El formato de archivo de la base de datos no se modifica para el modo WAL. Sin embargo, el archivo WAL y el índice wal son conceptos nuevos y, por lo tanto, las versiones anteriores de SQLite no sabrán cómo recuperar una base de datos SQLite bloqueada que estaba operando en modo WAL cuando ocurrió el bloqueo. Para evitar que las versiones anteriores de SQLite (anteriores a la versión 3.7.0, 2010-07-22) intenten recuperar una base de datos en modo WAL (y empeorar las cosas), los números de versión del formato de archivo de la base de datos (bytes 18 y 19 en el encabezado de la base de datos) ) se incrementan de 1 a 2 en el modo WAL. Por lo tanto, si una versión anterior de SQLite intenta conectarse a una base de datos SQLite que está operando en modo WAL, informará un error como “el archivo está encriptado o no es una base de datos”.

Uno puede cambiar explícitamente fuera del modo WAL usando un pragma como este:

PRAGMA journal_mode=DELETE;

El cambio deliberado del modo WAL cambia los números de versión del formato del archivo de la base de datos a 1 para que las versiones anteriores de SQLite puedan acceder nuevamente al archivo de la base de datos.