1. Vue d'ensemble2. Comment fonctionne WAL2.1. Point de contrôle2.2. Concurrence2.3. Considérations sur les performances3. Activation et configuration du mode WAL3.1. Point de contrôle automatique3.2. Points de contrôle initiés par l'application3.3. Persistance du mode WAL4. Le fichier WAL5. Bases de données en lecture seule6. Éviter les fichiers WAL trop volumineux7. Implémentation de la mémoire partagée pour l'index WAL8. Utilisation de WAL sans mémoire partagée9. Parfois les requêtes retournent SQLITE_BUSY en mode WAL10. Compatibilité ascendante

La méthode par défaut par laquelle SQLite implémente le commit et le rollback atomiques est un journal de rollback. En commençant par la version 3.7.0 (2010-07-21), une nouvelle option "Write-Ahead Log" (ci-après dénommée "WAL") est disponible.

Il y a des avantages et des inconvénients à utiliser WAL au lieu d'un journal de retour en arrière. Les avantages comprennent :

  1. WAL est significativement plus rapide dans la plupart des scénarios.
  2. WAL fournit plus de concurrence car les lecteurs ne bloquent pas les écrivains et un écrivain ne bloque pas les lecteurs. La lecture et l'écriture peuvent se dérouler simultanément.
  3. Les opérations d'entrée/sortie sur disque ont tendance à être plus séquentielles en utilisant WAL.
  4. WAL utilise beaucoup moins d'opérations fsync() et est donc moins vulnérable aux problèmes sur les systèmes où l'appel système fsync() est cassé.

Mais il y a aussi des inconvénients :

  1. WAL nécessite normalement que le VFS supporte les primitives de mémoire partagée. (Exception : WAL sans mémoire partagée) Les VFS intégrés d'unix et de windows supportent cela mais les VFS d'extension tiers pour les systèmes d'exploitation personnalisés pourraient ne pas le faire.
  2. Tous les processus utilisant une base de données doivent être sur le même ordinateur hôte ; WAL ne fonctionne pas sur un système de fichiers réseau.
  3. Les transactions qui impliquent des modifications contre plusieurs bases de données ATTACHées sont atomiques pour chaque base de données individuelle, mais ne sont pas atomiques sur toutes les bases de données en tant qu'ensemble.
  4. Il n'est pas possible de modifier la taille de page après être entré en mode WAL, que ce soit sur une base de données vide ou en utilisant VACUUM ou en restaurant à partir d'une sauvegarde en utilisant l'API de sauvegarde. Vous devez être en mode journal rollback pour modifier la taille de page.
  5. Il n'est pas possible d'ouvrir des bases de données WAL en lecture seule. Le processus d'ouverture doit avoir des privilèges d'écriture pour "-shm" wal-index fichier de mémoire partagée associé à la base de données, si ce fichier existe, ou bien un accès en écriture sur le répertoire contenant le fichier de la base de données si le fichier "-shm" n'existe pas. Commençant par version 3.22.0 (2018-01-22), un fichier de base de données en mode WAL en lecture seule peut être ouvert si l'attribut -shm et -wal fichiers existent déjà ou ces fichiers peuvent être créés ou la base de données est immuable.
  6. WAL pourrait être très légèrement plus lent (peut-être 1% ou 2% plus lent) que l'approche traditionnelle rollback-journal dans les applications qui font surtout des lectures et rarement des écritures.
  7. Il y a un " quasi-persistant " supplémentaire.-wal" et "-shm" de mémoire partagée associés à chaque base de données, ce qui peut rendre SQLite moins attrayant pour une utilisation en tant que format de fichier d'application.
  8. Il y a l'opération supplémentaire de checkpointing qui, bien qu'automatique par défaut, est toujours quelque chose dont les développeurs d'applications doivent être conscients.
  9. WAL fonctionne mieux avec de petites transactions. WAL ne fonctionne pas bien pour les très grosses transactions. Pour les transactions supérieures à environ 100 mégaoctets, les modes de journal de rollback traditionnels seront probablement plus rapides. Pour les transactions de plus d'un gigaoctet, le mode WAL peut échouer avec une erreur d'E/S ou de disque plein. Il est recommandé d'utiliser l'un des modes de journal de rollback pour les transactions supérieures à quelques dizaines de mégaoctets. Commençant par version 3.11.0 (2016-02-15), le mode WAL fonctionne aussi efficacement avec les transactions importantes que le mode rollback.

Le journal de rollback traditionnel fonctionne en écrivant une copie du contenu original inchangé de la base de données dans un fichier de journal de rollback séparé, puis en écrivant les modifications directement dans le fichier de base de données. Dans le cas d'un crash ou d'un ROLLBACK, le contenu original contenu dans le journal de rollback est lu dans le fichier de base de données afin de rétablir le fichier de base de données dans son état original. Le COMMIT se produit lorsque le journal de rollback est supprimé.

L'approche WAL inverse ce phénomène. Le contenu original est préservé dans le fichier de base de données et les modifications sont annexées dans un fichier WAL séparé. Un COMMIT se produit lorsqu'un enregistrement spécial indiquant un commit est ajouté au WAL. Ainsi, un COMMIT peut se produire sans jamais écrire dans la base de données d'origine, ce qui permet aux lecteurs de continuer à opérer à partir de la base de données d'origine non modifiée pendant que les changements sont simultanément livrés dans le WAL. Plusieurs transactions peuvent être annexées à la fin d'un seul fichier WAL.

2.1. Checkpointing

Bien sûr, on veut éventuellement transférer toutes les transactions qui sont ajoutées dans le fichier WAL vers la base de données originale. Replacer les transactions du fichier WAL dans la base de données s'appelle une "point de contrôle".

Une autre façon de penser à la différence entre rollback et write-ahead log est que dans l'approche rollback-journal, il y a deux opérations primitives, la lecture et l'écriture, alors qu'avec un write-ahead log il y a maintenant trois opérations primitives : la lecture, l'écriture et le checkpoint.

Par défaut, SQLite effectue un checkpoint automatiquement lorsque le fichier WAL atteint une taille seuil de 1000 pages. (L'option de compilation SQLITE_DEFAULT_WAL_AUTOCHECKPOINT peut être utilisée pour spécifier un défaut différent). Les applications utilisant WAL n'ont rien à faire pour que ces points de contrôle se produisent. Mais si elles le souhaitent, elles peuvent ajuster le seuil de point de contrôle automatique. Ou elles peuvent désactiver les points de contrôle automatiques et exécuter les points de contrôle pendant les moments d'inactivité ou dans un thread ou un processus séparé.

2.2. Concurrence

Quand une opération de lecture commence sur une base de données en mode WAL, elle se souvient d'abord de l'emplacement du dernier enregistrement commit valide dans le WAL. Appelez ce point la "marque de fin". Parce que le WAL peut croître et ajouter de nouveaux enregistrements de validation pendant que divers lecteurs se connectent à la base de données, chaque lecteur peut potentiellement avoir sa propre marque de fin. Mais pour tout lecteur particulier, la marque de fin reste inchangée pendant toute la durée de la transaction, ce qui garantit qu'une seule transaction de lecture ne voit que le contenu de la base de données tel qu'il existait à un seul moment.

Lorsqu'un lecteur a besoin d'une page de contenu, il vérifie d'abord le WAL pour voir si cette page y figure, et si c'est le cas, il tire la dernière copie de la page qui apparaît dans le WAL avant la marque de fin du lecteur. Si aucune copie de la page n'existe dans le WAL avant le point final du lecteur, la page est lue à partir du fichier de base de données original. Les lecteurs peuvent exister dans des processus séparés, donc pour éviter de forcer chaque lecteur à parcourir la totalité du WAL à la recherche de pages (le fichier WAL peut atteindre plusieurs mégaoctets, selon la fréquence d'exécution des points de contrôle), une structure de données appelée "wal-index" est maintenue dans la mémoire partagée qui aide les lecteurs à localiser les pages dans le WAL rapidement et avec un minimum d'E/S. Le wal-index améliore grandement les performances des lecteurs, mais l'utilisation de la mémoire partagée signifie que tous les lecteurs doivent exister sur la même machine. C'est pourquoi l'implémentation du journal write-ahead ne fonctionnera pas sur un système de fichiers réseau.

Les écrivains ajoutent simplement un nouveau contenu à la fin du fichier WAL. Parce que les écrivains ne font rien qui pourrait interférer avec les actions des lecteurs, les écrivains et les lecteurs peuvent s'exécuter en même temps. Cependant, comme il n'y a qu'un seul fichier WAL, il ne peut y avoir qu'un seul écrivain à la fois.

Une opération de point de contrôle prend le contenu du fichier WAL et le transfère à nouveau dans le fichier de base de données d'origine. Un point de contrôle peut s'exécuter simultanément avec des lecteurs, cependant le point de contrôle doit s'arrêter lorsqu'il atteint une page dans le WAL qui est au-delà de la marque de fin de tout lecteur actuel. Le point de contrôle doit s'arrêter à ce point car sinon il pourrait écraser une partie du fichier de base de données que le lecteur utilise activement. Le point de contrôle se souvient (dans l'index wal) du chemin parcouru et reprendra le transfert du contenu du WAL vers la base de données à partir de là où il s'est arrêté lors de la prochaine invocation.

Ainsi, une transaction de lecture de longue durée peut empêcher un checkpointer de progresser. Mais on peut supposer que chaque transaction de lecture finira par se terminer et que le checkpointer pourra continuer.

Chaque fois qu'une opération d'écriture se produit, le rédacteur vérifie la progression du checkpointer, et si la totalité du WAL a été transférée dans la base de données et synchronisée et si aucun lecteur n'utilise le WAL, alors le rédacteur rembobine le WAL jusqu'au début et commence à placer de nouvelles transactions au début du WAL. Ce mécanisme empêche un fichier WAL de croître sans limite.

2.3. Considérations sur les performances

Les transactions d'écriture sont très rapides car elles n'impliquent l'écriture du contenu qu'une seule fois (contre deux fois pour les transactions rollback-journal) et parce que les écritures sont toutes séquentielles. De plus, la synchronisation du contenu sur le disque n'est pas nécessaire, tant que l'application est prête à sacrifier la durabilité après une perte de courant ou un redémarrage dur. (Les écrivains synchronisent le WAL à chaque commit de transaction si PRAGMA synchrone est défini sur FULL mais omettent cette synchronisation si PRAGMA synchrone est défini sur NORMAL).

D'autre part, les performances de lecture se détériorent au fur et à mesure que la taille du fichier WAL augmente puisque chaque lecteur doit vérifier le contenu du fichier WAL et le temps nécessaire pour vérifier le fichier WAL est proportionnel à la taille du fichier WAL. Le wal-index aide à trouver le contenu du fichier WAL beaucoup plus rapidement, mais les performances diminuent toujours avec l'augmentation de la taille du fichier WAL. Par conséquent, pour maintenir de bonnes performances de lecture, il est important de maintenir la taille du fichier WAL basse en exécutant des points de contrôle à intervalles réguliers.

Le checkpointing nécessite des opérations de synchronisation afin d'éviter la possibilité de corruption de la base de données après une perte de puissance ou un redémarrage dur. Le WAL doit être synchronisé avec le stockage persistant avant de déplacer le contenu du WAL dans la base de données et le fichier de base de données doit être synchronisé avant de réinitialiser le WAL. Le point de contrôle nécessite également plus de recherche. Le pointeur de contrôle s'efforce d'effectuer le plus grand nombre possible d'écritures séquentielles de pages dans la base de données (les pages sont transférées du WAL vers la base de données dans un ordre croissant), mais même dans ce cas, il y aura généralement de nombreuses opérations de recherche entre les écritures de pages. Ces facteurs se combinent pour rendre les points de contrôle plus lents que les transactions d'écriture.

La stratégie par défaut est de permettre aux transactions d'écriture successives de faire croître le WAL jusqu'à ce que celui-ci atteigne une taille d'environ 1000 pages, puis d'exécuter une opération de point de contrôle pour chaque COMMIT ultérieur jusqu'à ce que le WAL soit réinitialisé pour être plus petit que 1000 pages. Par défaut, le point de contrôle sera exécuté automatiquement par le même thread qui effectue le COMMIT qui pousse le WAL au-delà de sa limite de taille. Ceci a pour effet de rendre la plupart des opérations COMMIT très rapides mais les COMMIT occasionnels (ceux qui déclenchent un point de contrôle) beaucoup plus lents. Si cet effet est indésirable, alors l'application peut désactiver le checkpointing automatique et exécuter les checkpoints périodiques dans un thread séparé, ou un processus séparé. (Les liens vers les commandes et les interfaces permettant d'accomplir cela sont indiqués ci-dessous).

Notez qu'avec PRAGMA synchrone réglé sur NORMAL, le point de contrôle est la seule opération à émettre une barrière d'E/S ou une opération de synchronisation (fsync() sur unix ou FlushFileBuffers() sur windows). Si une application exécute donc le checkpoint dans un thread ou un processus séparé, le thread ou processus principal qui effectue les requêtes et mises à jour de la base de données ne se bloquera jamais sur une opération de synchronisation. Cela permet d'éviter le "latch-up" dans les applications fonctionnant sur un lecteur de disque occupé. L'inconvénient de cette configuration est que les transactions ne sont plus durables et qu'elles risquent de faire marche arrière à la suite d'une panne de courant ou d'une réinitialisation matérielle.

Remarquez également qu'il existe un compromis entre les performances moyennes en lecture et les performances moyennes en écriture. Pour maximiser la performance de lecture, on veut garder le WAL aussi petit que possible et donc exécuter des points de contrôle fréquemment, peut-être aussi souvent que chaque COMMIT. Pour maximiser la performance d'écriture, on veut amortir le coût de chaque point de contrôle sur autant d'écritures que possible, ce qui signifie qu'on veut exécuter des points de contrôle peu fréquemment et laisser le WAL grandir autant que possible avant chaque point de contrôle. La décision de la fréquence d'exécution des points de contrôle peut donc varier d'une application à l'autre en fonction des exigences relatives de performance en lecture et en écriture de l'application. La stratégie par défaut consiste à exécuter un checkpoint une fois que le WAL atteint 1000 pages et cette stratégie semble bien fonctionner dans les applications de test sur les stations de travail, mais d'autres stratégies pourraient mieux fonctionner sur différentes plateformes ou pour différentes charges de travail.

Une connexion à une base de données SQLite a par défaut le mode journal_mode=DELETE. Pour la convertir en mode WAL, utilisez le pragme suivant :

PRAGMA journal_mode=WAL;

Le pragme journal_mode renvoie une chaîne de caractères qui est le nouveau mode de journal. En cas de succès, le pragme renvoie la chaîne "wal". Si la conversion en WAL n'a pas pu être achevée (par exemple, si le VFS ne prend pas en charge les primitives de mémoire partagée nécessaires), alors le mode de journalisation sera inchangé et la chaîne renvoyée par la primitive sera le mode de journalisation antérieur (par exemple "delete").

3.1. Point de contrôle automatique

Par défaut, SQLite effectuera automatiquement un checkpoint à chaque fois qu'un COMMIT se produira et que le fichier WAL atteindra une taille de 1000 pages ou plus, ou lorsque la dernière connexion à la base de données sur un fichier de base de données sera fermée. La configuration par défaut est destinée à fonctionner correctement pour la plupart des applications. Mais les programmes qui veulent plus de contrôle peuvent forcer un point de contrôle en utilisant le pragma wal_checkpoint ou en appelant l'interface C sqlite3_wal_checkpoint(). Le seuil du checkpoint automatique peut être modifié ou le checkpoint automatique peut être complètement désactivé en utilisant le pragma wal_autocheckpoint ou en appelant l'interface C sqlite3_wal_autocheckpoint(). Un programme peut également utiliser sqlite3_wal_hook() pour enregistrer un callback qui sera invoqué à chaque fois qu'une transaction s'engage dans le WAL. Ce callback peut alors invoquer sqlite3_wal_checkpoint() ou sqlite3_wal_checkpoint_v2() en fonction des critères qu'il juge appropriés. (Le mécanisme de checkpoint automatique est implémenté comme un simple wrapper autour de sqlite3_wal_hook().)

3.2. Points de contrôle initiés par l'application

Une application peut initier un checkpoint en utilisant n'importe quelle connexion inscriptible sur la base de données, simplement en invoquant sqlite3_wal_checkpoint() ou sqlite3_wal_checkpoint_v2(). Il existe trois sous-types de points de contrôle qui varient dans leur agressivité : PASSIF, FULL, et RESTART. Le style de checkpoint par défaut est PASSIF, qui fait autant de travail que possible sans interférer avec les autres connexions à la base de données, et qui peut ne pas s'exécuter jusqu'à la fin s'il y a des lecteurs ou des écrivains concurrents. Tous les points de contrôle initiés par sqlite3_wal_checkpoint() et par le mécanisme de point de contrôle automatique sont PASSIFS. Les checkpoints FULL et RESTART essaient plus difficilement d'exécuter le checkpoint jusqu'à son terme et ne peuvent être initiés que par un appel à sqlite3_wal_checkpoint_v2(). Voir la documentation sqlite3_wal_checkpoint_v2() pour des informations supplémentaires sur les points de contrôle FULL et RESTART.

3.3. Persistance du mode WAL

Contrairement aux autres modes de journalisation, PRAGMA journal_mode=WAL est persistant. Si un processus définit le mode WAL, puis ferme et rouvre la base de données, la base de données reviendra en mode WAL. En revanche, si un processus définit (par exemple) PRAGMA journal_mode=TRUNCATE, puis ferme et rouvre la base de données, celle-ci reviendra dans le mode de rollback par défaut de DELETE plutôt que dans le paramètre TRUNCATE précédent.

La persistance du mode WAL signifie que les applications peuvent être converties pour utiliser SQLite en mode WAL sans apporter aucune modification à l'application elle-même. Il suffit d'exécuter "PRAGMA journal_mode=WAL;" sur le(s) fichier(s) de la base de données en utilisant le shell en ligne de commande ou un autre utilitaire, puis de redémarrer l'application.

Le mode journal WAL sera défini sur toutes les connexions au même fichier de base de données s'il est défini sur une connexion quelconque.

Pendant qu'une connexion à une base de données est ouverte sur une base de données en mode WAL, SQLite maintient un fichier journal supplémentaire appelé "Write Ahead Log" ou "fichier WAL". Le nom de ce fichier sur le disque est généralement le nom du fichier de la base de données avec un supplément "-wal", bien que des règles de nommage différentes puissent s'appliquer si SQLite est compilé avec SQLITE_ENABLE_8_3_NAMES.

Le fichier WAL existe aussi longtemps que toute connexion à la base de données a la base de données ouverte. Habituellement, le fichier WAL est supprimé automatiquement lorsque la dernière connexion à la base de données se ferme. Cependant, si le dernier processus à avoir la base de données ouverte quitte sans fermer proprement la connexion à la base de données, ou si le contrôle SQLITE_FCNTL_PERSIST_WALfile est utilisé, alors le fichier WAL peut être conservé sur le disque après que toutes les connexions à la base de données aient été fermées. Le fichier WAL fait partie de l'état persistant de la base de données et doit être conservé avec la base de données si celle-ci est copiée ou déplacée. Si un fichier de base de données est séparé de son fichier WAL, les transactions qui ont été précédemment engagées dans la base de données peuvent être perdues, ou le fichier de base de données peut être corrompu. La seule façon sûre de supprimer un fichier WAL est d'ouvrir le fichier de base de données en utilisant l'une des interfaces sqlite3_open() puis de fermer immédiatement la base de données en utilisant sqlite3_close().

Le format du fichier WAL est précisément défini et est multiplateforme.

Les anciennes versions de SQLite ne pouvaient pas lire une base de données en mode WAL qui était en lecture seule. En d'autres termes, un accès en écriture était nécessaire pour lire une base de données en mode WAL. Cette contrainte a été assouplie à partir de SQLite. version 3.22.0 (2018-01-22).

Sur les versions plus récentes de SQLite, une base de données en mode WAL sur un support en lecture seule, ou une base de données en mode WAL dépourvue de droits d'écriture, peut encore être lue tant qu'une ou plusieurs des conditions suivantes sont remplies :

  1. Le site -shm et -wal existent déjà et sont lisibles
  2. Il existe des droits d'écriture sur le répertoire contenant la base de données, de sorte que les fichiers -shm et -wal puissent être créés.
  3. La connexion à la base de données est ouverte en utilisant le paramètre de requête immuable.

Même s'il est possible d'ouvrir une base de données en mode WAL en lecture seule, il est de bonne pratique de convertir en PRAGMA journal_mode=DELETE avant de graver une image de base de données SQLite sur un support en lecture seule.

Dans les cas normaux, le nouveau contenu est ajouté au fichier WAL jusqu'à ce que le fichier WAL accumule environ 1000 pages (et a donc une taille d'environ 4MB), point auquel un checkpoint est automatiquement exécuté et le fichier WAL est recyclé. Le point de contrôle ne tronque normalement pas le fichier WAL (à moins que le pragma journal_size_limit soit défini). Au lieu de cela, il fait simplement en sorte que SQLite commence à écraser le fichier WAL depuis le début. Ceci est fait parce qu'il est normalement plus rapide d'écraser un fichier existant que de l'ajouter. Lorsque la dernière connexion à une base de données se ferme, cette connexion effectue un dernier checkpoint, puis supprime le WAL et son fichier de mémoire partagée associé, pour nettoyer le disque.

Ainsi, dans la grande majorité des cas, les applications n'ont pas à se soucier du tout du fichier WAL. SQLite s'en occupera automatiquement. Mais il est possible d'amener SQLite dans un état où le fichier WAL va croître sans limite, provoquant une utilisation excessive de l'espace disque et un ralentissement de la vitesse des requêtes. Les puces suivantes énumèrent certaines des façons dont cela peut se produire et comment les éviter.

  • Désactiver le mécanisme de point de contrôle automatique. Dans sa configuration par défaut, SQLite effectue un checkpoint du fichier WAL à la conclusion de toute transaction lorsque le fichier WAL a plus de 1000 pages. Cependant, il existe des options de compilation et d'exécution qui peuvent désactiver ou différer ce point de contrôle automatique. Si une application désactive le point de contrôle automatique, alors il n'y a rien pour empêcher le fichier WAL de croître excessivement.

  • La famine du point de contrôle. Un point de contrôle ne peut s'exécuter jusqu'à son terme, et réinitialiser le fichier WAL, que si aucune autre connexion de base de données n'utilise le fichier WAL. Si une autre connexion a une transaction de lecture ouverte, alors le point de contrôle ne peut pas réinitialiser le fichier WAL car cela pourrait supprimer le contenu sous le lecteur. Le point de contrôle effectue autant de travail que possible sans perturber le lecteur, mais il ne peut pas s'exécuter jusqu'à la fin. Le point de contrôle redémarre là où il s'est arrêté après la prochaine transaction d'écriture. Cela se répète jusqu'à ce qu'un certain checkpoint soit capable de s'achever.

    Cependant, si une base de données a de nombreux lecteurs concurrents qui se chevauchent et qu'il y a toujours au moins un lecteur actif, alors aucun point de contrôle ne pourra se terminer et donc le fichier WAL grandira sans limite.

    Ce scénario peut être évité en s'assurant qu'il y a des " reader gaps " : des moments où aucun processus ne lit la base de données et que des checkpoints sont tentés pendant ces moments. Dans les applications avec de nombreux lecteurs simultanés, on peut également envisager d'exécuter des points de contrôle manuels avec l'option SQLITE_CHECKPOINT_RESTART ou SQLITE_CHECKPOINT_TRUNCATE qui garantira que le point de contrôle s'exécute jusqu'à la fin avant de revenir. L'inconvénient de l'utilisation de SQLITE_CHECKPOINT_RESTART et SQLITE_CHECKPOINT_TRUNCATE est que les lecteurs pourraient se bloquer pendant l'exécution du point de contrôle.

  • Transactions d'écriture très importantes. Un checkpoint ne peut se terminer que lorsqu'aucune autre transaction n'est en cours d'exécution, ce qui signifie que le fichier WAL ne peut pas être réinitialisé au milieu d'une transaction d'écriture. Ainsi, une modification importante d'une grande base de données peut entraîner un grand fichier WAL. Le fichier WAL sera checkpointé une fois la transaction d'écriture terminée (en supposant qu'il n'y a pas d'autres lecteurs qui le bloquent) mais entre-temps, le fichier peut devenir très gros.

    A partir de SQLite version 3.11.0 (2016-02-15), le fichier WAL pour une seule transaction devrait avoir une taille proportionnelle à la transaction elle-même. Les pages qui sont modifiées par la transaction ne devraient être écrites dans le fichier WAL qu'une seule fois. Cependant, avec les anciennes versions de SQLite, la même page pourrait être écrite dans le fichier WAL plusieurs fois si la transaction devient plus grande que le cache des pages.

Le wal-index est implémenté en utilisant un fichier ordinaire qui est mmappé pour la robustesse. Les premières implémentations (préversion) du mode WAL stockaient le wal-index dans de la mémoire partagée volatile, comme des fichiers créés dans /dev/shm sous Linux ou /tmp sur d'autres systèmes unix. Le problème avec cette approche est que les processus ayant un répertoire racine différent (modifié via la commande chroot) verront différents fichiers et utiliseront donc différentes zones de mémoire partagée, ce qui entraînera une corruption de la base de données. D'autres méthodes pour créer des blocs de mémoire partagée sans nom ne sont pas portables à travers les différentes saveurs d'unix. Et nous n'avons pas trouvé de méthode pour créer des blocs de mémoire partagée sans nom sous windows. La seule façon que nous avons trouvée pour garantir que tous les processus accédant au même fichier de base de données utilisent la même mémoire partagée est de créer la mémoire partagée en mappant un fichier dans le même répertoire que la base de données elle-même.

L'utilisation d'un fichier disque ordinaire pour fournir de la mémoire partagée présente l'inconvénient d'effectuer des E/S disque inutiles en écrivant la mémoire partagée sur le disque. Cependant, les développeurs ne pensent pas que cela soit un problème majeur puisque la taille du wal-index dépasse rarement 32 KiB et n'est jamais synchronisée. En outre, le fichier de sauvegarde wal-index est supprimé lorsque la dernière connexion à la base de données se déconnecte, ce qui empêche souvent toute véritable E/S disque de se produire.

Les applications spécialisées pour lesquelles l'implémentation par défaut de la mémoire partagée est inacceptable peuvent concevoir des méthodes alternatives via un VFS personnalisé. Par exemple, si l'on sait qu'une base de données particulière ne sera accédée que par des threads au sein d'un seul processus, le wal-index peut être implémenté en utilisant la mémoire de tas au lieu de la vraie mémoire partagée.

Début dans SQLite version 3.7.4 (2010-12-07), les bases de données WAL peuvent être créées, lues et écrites même si la mémoire partagée est indisponible, tant que le locking_mode est défini sur EXCLUSIVE avant la première tentative d'accès. En d'autres termes, un processus peut interagir avec une base de données WAL sans utiliser la mémoire partagée si ce processus est garanti comme étant le seul processus accédant à la base de données. Cette fonctionnalité permet aux bases de données WAL d'être créées, lues et écrites par les VFS hérités qui ne disposent pas des méthodes de mémoire partagée "version 2" xShmMap, xShmLock, xShmBarrier et xShmUnmap sur l'objet sqlite3_io_methods.

Si le mode de verrouillage EXCLUSIF est défini avant le premier accès à la base de données en mode WAL, alors SQLite ne tente jamais d'appeler aucune des méthodes de mémoire partagée et donc aucun wal-index de mémoire partagée n'est jamais créé. Dans ce cas, la connexion à la base de données reste en mode EXCLUSIF tant que le mode journal est WAL ; les tentatives de changer le mode de verrouillage en utilisant "PRAGMA locking_mode=NORMAL;" sont sans effet. La seule façon de sortir du mode de verrouillage EXCLUSIF est de sortir d'abord du mode journal WAL.

Si le mode de verrouillage NORMAL est en vigueur pour le premier accès à la base de données en mode WAL, alors le wal-index à mémoire partagée est créé. Cela signifie que le VFS sous-jacent doit supporter la mémoire partagée "version 2". Si le VFS ne supporte pas les méthodes de mémoire partagée, alors la tentative d'ouvrir une base de données qui est déjà en mode WAL, ou la tentative de convertir une base de données en mode WAL, échouera. Tant qu'une seule connexion utilise un wal-index à mémoire partagée, le mode de verrouillage peut être modifié librement entre NORMAL et EXCLUSIF. C'est seulement lorsque le wal-index à mémoire partagée est omis, lorsque le mode de verrouillage est EXCLUSIF avant le premier accès à la base de données en mode WAL, que le mode de verrouillage est bloqué en EXCLUSIF.

Le deuxième avantage du mode WAL est que les écrivains ne bloquent pas les lecteurs et les lecteurs de ne pas bloquer les écrivains. Ceci est principalement vrai. Mais il y a quelques cas obscurs où une requête contre une base de données en mode WAL peut retourner SQLITE_BUSY, donc les applications devraient être préparées à cette éventualité.

Les cas où une requête contre une base de données en mode WAL peut retourner SQLITE_BUSY sont notamment les suivants :

  • Si une autre connexion de base de données a le mode de base de données ouvert en mode de verrouillage exclusif, alors toutes les requêtes contre la base de données retourneront SQLITE_BUSY. Chrome et Firefox ouvrent tous deux leurs fichiers de base de données en mode de verrouillage exclusif, de sorte que les tentatives de lecture des bases de données de Chrome ou de Firefox pendant que les applications sont en cours d'exécution se heurteront à ce problème, par exemple.

  • Lorsque la dernière connexion à une base de données particulière se ferme, cette connexion acquiert un verrouillage exclusif pendant une courte période pendant qu'elle nettoie les fichiers WAL et de mémoire partagée. Si une deuxième base de données essaie d'ouvrir et d'interroger la base de données alors que la première connexion est encore au milieu de son processus de nettoyage, la deuxième connexion pourrait obtenir une erreur SQLITE_BUSY.

  • Si la dernière connexion à une base de données a planté, alors la première nouvelle connexion pour ouvrir la base de données commencera un processus de récupération. Un verrou exclusif est maintenu pendant la récupération. Donc, si une troisième connexion à la base de données essaie de sauter et d'interroger pendant que la deuxième connexion exécute la récupération, la troisième connexion obtiendra une erreur SQLITE_BUSY.

Le format du fichier de base de données est inchangé pour le mode WAL. Cependant, le fichier WAL et le wal-index sont de nouveaux concepts et donc les anciennes versions de SQLite ne sauront pas comment récupérer une base de données SQLite plantée qui fonctionnait en mode WAL lorsque le plantage s'est produit. Pour empêcher les anciennes versions de SQLite (antérieures à la version 3.7.0, 2010-07-22) d'essayer de récupérer une base de données en mode WAL (et d'aggraver la situation), les numéros de version du format de fichier de la base de données (octets 18 et 19 dans l'en-tête de la base de données) passent de 1 à 2 en mode WAL. Ainsi, si une ancienne version de SQLite tente de se connecter à une base de données SQLite qui fonctionne en mode WAL, elle signalera une erreur du type "le fichier est crypté ou n'est pas une base de données".

On peut explicitement sortir du mode WAL en utilisant un pragma tel que celui-ci :

PRAGMA journal_mode=DELETE;

En quittant délibérément le mode WAL, les numéros de version du format du fichier de base de données reviennent à 1, de sorte que les anciennes versions de SQLite peuvent à nouveau accéder au fichier de base de données.