1. Écraser un fichier par un thread ou un processus malveillant1.1. Continuer à utiliser un descripteur de fichier après qu'il ait été fermé1.2. Sauvegarde ou restauration pendant qu'une transaction est active1.3. Suppression d'un journal à chaud1.4. Mauvaise correspondance entre les fichiers de base de données et les journaux à chaud 2. Problèmes de verrouillage de fichiers2.1. Systèmes de fichiers avec des implémentations de verrouillage cassées ou manquantes2.2. Verrous consultatifs Posix annulés par un thread séparé effectuant close()2.2.1. Plusieurs copies de SQLite liées à la même application2.3. Deux processus utilisant des protocoles de verrouillage différents2.4. Déverrouillage ou renommage d'un fichier de base de données en cours d'utilisation2.5. Liens multiples vers le même fichier2.6. Transport d'une connexion de base de données ouverte à travers un fork() 3. Échec de la synchronisation3.1. Disques durs qui n'honorent pas les demandes de synchronisation3.2. Désactiver la synchronisation en utilisant PRAGMAs4. Défaillances du lecteur de disque et de la mémoire flash4.1. Contrôleurs de mémoire flash non sécurisés4.2. Fausse capacité des clés USB5. Corruption de la mémoire6. Autres problèmes liés au système d'exploitation6.1. Threads Linux6.2. Échecs de mmap() sur QNX6.3. Corruption du système de fichiers7. Erreurs de configuration de SQLite8. Bogues dans SQLite8.1. Faux rapports de corruption dus au rétrécissement de la base de données8.2. Corruption suite à des commutations entre les modes rollback et WAL8.3. Une erreur d'E/S lors de l'obtention d'un verrou entraîne une corruption8.4. Les pages de la base de données fuient de la liste des pages libres8.5. Corruption suite à des écritures alternées de 3.6 et 3.7.8.6. Race condition dans la récupération sur système windows.

Vue d'ensemble

Une base de données SQLite est très résistante à la corruption. Si un crash de l'application, ou un crash du système d'exploitation, ou même une panne de courant se produit au milieu d'une transaction, la transaction partiellement écrite devrait être automatiquement annulée lors du prochain accès au fichier de la base de données. Le processus de récupération est entièrement automatique et ne nécessite aucune action de la part de l'utilisateur ou de l'application.

Bien que SQLite soit résistant à la corruption des bases de données, il n'est pas immunisé. Ce document décrit les différentes façons dont une base de données SQLite peut être corrompue.

Les fichiers de base de données SQLite sont des fichiers disque ordinaires. Cela signifie que tout processus peut ouvrir le fichier et l'écraser avec des déchets. Il n'y a rien que la bibliothèque SQLite puisse faire pour se défendre contre cela.

1.1. Continuer à utiliser un descripteur de fichier après qu'il ait été fermé.

Nous avons vu de multiples cas où un descripteur de fichier était ouvert sur un fichier, puis ce descripteur de fichier a été fermé et rouvert sur une base de données SQLite. Plus tard, un autre thread a continué à écrire dans l'ancien descripteur de fichier, sans se rendre compte que le fichier original avait déjà été fermé. Mais parce que le descripteur de fichier avait été rouvert par SQLite, les informations qui étaient destinées à aller dans le fichier original ont fini par écraser des parties de la base de données SQLite, conduisant à la corruption de la base de données.

Un exemple de ceci s'est produit vers 2013-08-30 sur le référentiel canonique de la base de données de la base de données de l'utilisateur. Fossil DVCS. Dans cet événement, le descripteur de fichier 2 (erreur standard) était fermé par erreur (par stunnel nous le soupçonnons) avant sqlite3_open_v2(), de sorte que le descripteur de fichier utilisé pour le fichier de base de données du dépôt était 2. Plus tard, un bogue d'application a fait qu'une instruction assert() a émis un message d'erreur en invoquant write(2,...). Mais puisque le descripteur de fichier 2 était maintenant connecté à un fichier de base de données, le message d'erreur a écrasé une partie de la base de données. Pour se prémunir contre ce genre de problème, SQLite version 3.8.1 (2013-10-17) et les versions ultérieures refusent d'utiliser des descripteurs de fichiers à faible numérotation pour les fichiers de base de données. (Voir SQLITE_MINIMUM_FILE_DESCRIPTOR pour des informations supplémentaires).

Autre exemple de corruption causée par l'utilisation d'un descripteur de fichier fermé . rapporté par les ingénieurs de facebook. dans un billet de blog le 2014-08-12.

Un autre exemple de cette erreur a été signalé contre Fossil le 11 juillet 2019. Un descripteur de fichier était ouvert pour la sortie de débogage, mais ensuite fermé et rouvert par SQLite. Mais la logique de débogage continuait à écrire dans le descripteur de fichier d'origine. Voir le discussion du forum pour le rapport de bogue et un lien vers le correctif.

1.2. Sauvegarde ou restauration pendant qu'une transaction est active.

Les systèmes qui exécutent des sauvegardes automatiques en arrière-plan pourraient essayer de faire une copie de sauvegarde d'un fichier de base de données SQLite alors qu'il est au milieu d'une transaction. La copie de sauvegarde pourrait alors contenir une partie de l'ancien et une partie du nouveau contenu, et donc être corrompue.

La meilleure approche pour faire des copies de sauvegarde fiables d'une base de données SQLite est d'utiliser l'API de sauvegarde qui fait partie de la bibliothèque SQLite. A défaut, il est sûr de faire une copie d'un fichier de base de données SQLite tant qu'il n'y a pas de transactions en cours par un quelconque processus. Si la transaction précédente a échoué, il est alors important que tout journal de retour en arrière (la fonction *-journal ) ou le journal d'écriture en tête (le fichier *-wal ) soit copié avec le fichier de base de données lui-même.

1.3. Suppression d'un journal à chaud

SQLite stocke normalement tout le contenu dans un seul fichier disque. Cependant, pendant l'exécution d'une transaction, les informations nécessaires à la récupération de la base de données après un crash ou une panne de courant sont stockées dans des fichiers journaux auxiliaires. De tels fichiers journaux sont décrits comme étant "chauds". Les fichiers journaux ont le même nom que le fichier de base de données original avec l'ajout de -journal ou -wal suffixe.

SQLite doit voir les fichiers journaux pour pouvoir récupérer après un crash ou une panne de courant. Si les fichiers journaux chauds sont déplacés, supprimés ou renommés après un crash ou une panne de courant, alors la récupération automatique ne fonctionnera pas et la base de données peut devenir corrompue.

Une autre manifestation de ce problème est la corruption de la base de données causée par l'utilisation incohérente des noms de fichiers 8+3.

1.4. Fichiers de base de données et journaux chauds mal assortis.

L'exemple précédent est un cas spécifique d'un problème plus général : l'état d'une base de données SQLite est contrôlé à la fois par le fichier de base de données et le fichier journal. Dans un état quiescent, le fichier journal n'existe pas et seul le fichier de base de données compte. Mais si le fichier journal existe, il doit être conservé avec la base de données pour éviter toute corruption. Les actions suivantes sont toutes susceptibles d'entraîner une corruption :

  • Echanger les fichiers journaux entre deux bases de données différentes.
  • Écraser un fichier journal avec un fichier journal différent.
  • Déplacement d'un fichier journal d'une base de données à une autre.
  • Copie d'un fichier de base de données sans copier également son journal.
  • Écraser un fichier de base de données par un autre sans supprimer également tout journal chaud associé à la base de données d'origine.

SQLite utilise des verrous de fichier sur le fichier de base de données, et sur le journal d'écriture ou fichier WAL, pour coordonner l'accès entre les processus concurrents. Sans coordination, deux threads ou processus pourraient essayer d'apporter des modifications incompatibles à un fichier de base de données en même temps, ce qui entraînerait une corruption de la base de données.

2.1. Systèmes de fichiers avec des implémentations de verrouillage cassées ou manquantes.

SQLite dépend du système de fichiers sous-jacent pour effectuer le verrouillage comme la documentation l'indique. Mais certains systèmes de fichiers contiennent des bogues dans leur logique de verrouillage de sorte que les verrous ne se comportent pas toujours comme annoncé. Ceci est particulièrement vrai pour les systèmes de fichiers réseau et NFS en particulier. Si SQLite est utilisé sur un système de fichiers où les primitives de verrouillage contiennent des bogues, et si deux ou plusieurs threads ou processus essaient d'accéder à la même base de données en même temps, alors la corruption de la base de données pourrait en résulter.

2.2. Verrous consultatifs Posix annulés par un thread séparé faisant close().

Le mécanisme de verrouillage par défaut utilisé par SQLite sur les plateformes unix est le verrouillage consultatif POSIX. Malheureusement, le verrouillage consultatif POSIX a des bizarreries de conception qui le rendent sujet à une mauvaise utilisation et à l'échec. En particulier, tout thread dans le même processus avec un descripteur de fichier qui détient un verrou consultatif POSIX peut remplacer ce verrou en utilisant un descripteur de fichier différent. Un problème particulièrement pernicieux est que le verrou close() annule tous les verrous consultatifs POSIX sur le même fichier pour tous les threads et tous les descripteurs de fichiers dans le processus.

Ainsi, par exemple, supposons qu'un processus multithread ait deux ou plusieurs threads avec des connexions de base de données SQLite séparées sur le même fichier de base de données. Puis un troisième thread arrive et veut lire quelque chose de ce même fichier de base de données par lui-même, sans utiliser la bibliothèque SQLite. Le troisième thread fait un open(), a read() et ensuite un close(). On pourrait penser que cela est inoffensif. Mais le close() a fait tomber les verrous détenus sur la base de données par tous les autres threads. Ces autres threads n'ont aucun moyen de savoir que leurs verrous viennent d'être mis à la poubelle (POSIX ne fournit aucun mécanisme pour le déterminer) et ils continuent donc à fonctionner en supposant que leurs verrous sont toujours valides. Cela peut conduire à ce que deux ou plusieurs threads ou processus essaient d'écrire dans la base de données en même temps, ce qui entraîne une corruption de la base de données.

Notez qu'il est parfaitement sûr pour deux ou plusieurs threads d'accéder au même fichier de base de données SQLite en utilisant la bibliothèque SQLite. Les pilotes unix de SQLite connaissent les bizarreries du verrouillage consultatif POSIX et les contournent. Ce problème ne se pose que lorsqu'un thread tente de contourner la bibliothèque SQLite et de lire directement le fichier de base de données.

2.2.1. Plusieurs copies de SQLite liées dans la même application.

Comme indiqué dans le paragraphe précédent, SQLite prend des mesures pour contourner les bizarreries du verrouillage consultatif POSIX. Une partie de ce contournement implique de garder une liste globale (protégée par mutex) des fichiers de base de données SQLite ouverts. Mais, si plusieurs copies de SQLite sont liées à la même application, il y aura plusieurs instances de cette liste globale. Les connexions aux bases de données ouvertes à l'aide d'une copie de la bibliothèque SQLite ne seront pas au courant des connexions aux bases de données ouvertes à l'aide de l'autre copie, et ne seront pas en mesure de contourner les bizarreries du verrouillage consultatif POSIX. A close() sur une connexion pourrait, sans le savoir, lever les verrous sur une connexion de base de données différente, conduisant à une corruption de la base de données.

Le scénario ci-dessus semble tiré par les cheveux. Mais les développeurs de SQLite sont au courant d'au moins un produit commercial qui a été publié avec exactement ce bogue. Le vendeur est venu voir les développeurs de SQLite pour demander de l'aide afin de traquer certains problèmes peu fréquents de corruption de base de données qu'ils voyaient sur Linux et Mac. Le problème a finalement été attribué au fait que l'application était liée à deux copies distinctes de SQLite. La solution consistait à modifier les procédures de construction de l'application pour la lier à une seule copie de SQLite au lieu de deux.

2.3. Deux processus utilisant des protocoles de verrouillage différents.

Le mécanisme de verrouillage par défaut utilisé par SQLite sur les plateformes unix est le verrouillage consultatif POSIX, mais il existe d'autres options. En sélectionnant un sqlite3_vfs alternatif à l'aide de l'interface sqlite3_open_v2(), une application peut faire usage d'autres protocoles de verrouillage qui pourraient être plus appropriés à certains systèmes de fichiers. Par exemple, le verrouillage par fichier point pourrait être sélectionné pour être utilisé dans une application qui doit s'exécuter sur un système de fichiers NFS qui ne supporte pas le verrouillage consultatif POSIX.

Il est important que toutes les connexions au même fichier de base de données utilisent le même protocole de verrouillage. Si une application utilise des verrous consultatifs POSIX et qu'une autre application utilise un verrouillage de fichier par points, alors les deux applications ne verront pas les verrous de l'autre et ne pourront pas coordonner l'accès à la base de données, ce qui pourrait entraîner une corruption de la base de données.

2.4. Délier ou renommer un fichier de base de données en cours d'utilisation.

Si deux processus ont des connexions ouvertes au même fichier de base de données et qu'un processus ferme sa connexion, dé-lien le fichier, puis crée un nouveau fichier de base de données à sa place avec le même nom et rouvre le nouveau fichier, alors les deux processus parleront à différents fichiers de base de données avec le même nom. (Notez que ceci n'est possible que sur les systèmes Posix et similaires à Posix qui permettent de dissocier un fichier alors qu'il est encore ouvert en lecture et en écriture. Windows ne permet pas que cela se produise). Puisque les journaux de rollback et les fichiers WAL sont basés sur le nom du fichier de base de données, les deux fichiers de base de données différents partageront le même journal de rollback ou fichier WAL. Un rollback ou une restauration pour l'une des bases de données peut utiliser le contenu de l'autre base de données, ce qui entraîne une corruption. Un problème similaire se produit si un fichier de base de données est renommé pendant qu'il est ouvert et qu'un nouveau fichier est créé avec l'ancien nom.

En d'autres termes, délier ou renommer un fichier de base de données ouvert entraîne un comportement indéfini et probablement indésirable.

Commencer avec SQLite version 3.7.17 (2013-05-20), l'interface unix OS enverra des messages SQLITE_WARNING au journal des erreurs si un fichier de base de données est délié alors qu'il est toujours utilisé.

2.5. Liens multiples vers le même fichier.

Si un seul fichier de base de données a plusieurs liens (que ce soit des liens durs ou souples), alors c'est juste une autre façon de dire que le fichier a plusieurs noms. Si deux ou plusieurs processus ouvrent la base de données en utilisant des noms différents, alors ils utiliseront des journaux de rollback et des fichiers WAL différents. Cela signifie que si un processus se plante, l'autre processus sera incapable de récupérer la transaction en cours parce qu'il cherchera au mauvais endroit le journal approprié.

En d'autres termes, l'ouverture et l'utilisation d'un fichier de base de données qui a deux noms ou plus entraîne un comportement indéfini et probablement indésirable.

Commencer avec SQLite version 3.7.17 (2013-05-20), l'interface du système d'exploitation unix enverra des messages SQLITE_WARNING au journal des erreurs si un fichier de base de données a plusieurs liens durs.

Débutant avec SQLite version 3.10.0 (2016-01-06), l'interface unix OS tentera de résoudre les liens symboliques et d'ouvrir le fichier de base de données par son nom canonique. Avant la version 3.10.0, l'ouverture d'un fichier de base de données par un lien symbolique était similaire à l'ouverture d'un fichier de base de données qui avait plusieurs liens durs et entraînait un comportement non défini.

2.6. Transporter une connexion de base de données ouverte à travers un fork().

N'ouvrez pas une connexion à une base de données SQLite, puis fork(), puis essayez d'utiliser cette connexion à la base de données dans le processus enfant. Toutes sortes de problèmes de verrouillage en résulteront et vous pouvez facilement vous retrouver avec une base de données corrompue. SQLite n'est pas conçu pour supporter ce genre de comportement. Toute connexion de base de données qui est utilisée dans un processus enfant doit être ouverte dans le processus enfant, et non héritée du parent.

N'appelez même pas sqlite3_close() sur une connexion de base de données depuis un processus enfant si la connexion a été ouverte dans le parent. Il est sûr de fermer le descripteur de fichier sous-jacent, mais l'interface sqlite3_close() pourrait invoquer des activités de nettoyage qui supprimeront le contenu sous le parent, entraînant des erreurs et peut-être même une corruption de la base de données.

Afin de garantir que les fichiers de base de données sont toujours cohérents, SQLite demandera occasionnellement au système d'exploitation de vider toutes les écritures en attente vers le stockage persistant puis d'attendre que ce vidage soit terminé. Ceci est accompli en utilisant la fonction fsync() sous unix et l'appel système FlushFileBuffers() sous Windows. On appelle ce flush d'écritures en attente une "synchro".

En fait, si l'on ne se préoccupe que des écritures atomiques et cohérentes et que l'on est prêt à renoncer aux écritures durables, l'opération de synchronisation n'a pas besoin d'attendre que le contenu soit complètement stocké sur un support persistant. Au lieu de cela, l'opération de synchronisation peut être considérée comme une barrière d'entrée/sortie. Tant que toutes les écritures qui se produisent avant la synchronisation sont achevées avant toute écriture qui se produit après la synchronisation, aucune corruption de la base de données ne se produira. Si la synchronisation fonctionne comme une barrière d'E/S et non comme une véritable synchronisation, alors une panne de courant ou un crash du système pourrait provoquer le retour en arrière d'une ou plusieurs transactions précédemment engagées (en violation de la propriété "durable" de "ACID"), mais la base de données continuera au moins à être cohérente, et c'est ce dont la plupart des gens se soucient.

3.1. Disques durs qui n'honorent pas les demandes de synchronisation.

Malheureusement, la plupart des périphériques de stockage de masse de qualité grand public mentent au sujet de la synchronisation. Les lecteurs de disques signalent que le contenu est en sécurité sur le support persistant dès qu'il atteint le tampon de piste et avant d'être réellement écrit sur l'oxyde. Cela donne l'impression que les lecteurs de disques fonctionnent plus rapidement (ce qui est d'une importance vitale pour le fabricant afin qu'il puisse présenter de bons chiffres de référence dans les magazines spécialisés). Et en toute honnêteté, le mensonge ne cause normalement aucun dommage, tant qu'il n'y a pas de perte de puissance ou de réinitialisation dure avant que le tampon de piste ne soit effectivement écrit dans l'oxyde. Mais si une perte de puissance ou un hard reset se produit, et si cela a pour conséquence que le contenu écrit après une synchronisation atteint oxide alors que le contenu écrit avant la synchronisation est toujours dans un track buffer, alors une corruption de la base de données peut se produire.

Les clés USB à mémoire flash semblent être des menteurs particulièrement pernicieux en ce qui concerne les demandes de synchronisation. On peut facilement s'en rendre compte en commettant une transaction importante dans une base de données SQLite sur une clé USB. La commande COMMIT reviendra relativement rapidement, indiquant que la clé USB a dit au système d'exploitation et que le système d'exploitation a dit à SQLite que tout le contenu est en sécurité dans le stockage persistant, et pourtant la LED à l'extrémité de la clé USB continuera à clignoter pendant plusieurs secondes. Le fait de retirer la clé USB alors que la LED continue de clignoter entraîne fréquemment une corruption de la base de données.

Notez que SQLite doit croire tout ce que le système d'exploitation et le matériel lui disent sur l'état des demandes de synchronisation. Il n'y a aucun moyen pour SQLite de détecter que l'un ou l'autre ment et que les écritures peuvent se produire dans le désordre. Cependant, SQLite en mode WAL est beaucoup plus indulgent envers les écritures hors de l'ordre que dans les modes de journal de retour en arrière par défaut. En mode WAL, le seul moment où une opération de synchronisation échouée peut causer la corruption de la base de données est pendant une opération de checkpoint. Un échec de synchronisation pendant un COMMIT peut entraîner une perte de durabilité mais pas une corruption du fichier de base de données. Par conséquent, une ligne de défense contre la corruption de la base de données due à l'échec des opérations de synchronisation est d'utiliser SQLite en mode WAL et de faire un checkpoint aussi peu souvent que possible.

3.2. Désactiver la synchronisation en utilisant des PRAGMA

Les opérations de synchronisation que SQLite effectue pour aider à assurer l'intégrité peuvent être désactivées à l'exécution en utilisant le pragma synchrone. En définissant PRAGMA synchrone=OFF, toutes les opérations de synchronisation sont omises. Cela donne l'impression que SQLite s'exécute plus rapidement, mais cela permet également au système d'exploitation de réordonner librement les écritures, ce qui pourrait entraîner une corruption de la base de données si une panne de courant ou une réinitialisation matérielle se produit avant que tout le contenu n'atteigne le stockage persistant.

Pour une fiabilité maximale et pour la robustesse contre la corruption de la base de données, SQLite devrait toujours être exécuté avec son paramètre synchrone par défaut de FULL.

Une base de données SQLite peut être corrompue si le contenu du fichier change en raison d'une défaillance du disque dur ou de la mémoire flash. C'est très rare, mais les disques peuvent occasionnellement basculer un bit au milieu d'un secteur.

4.1. Contrôleurs de mémoire flash non protégés en puissance

On nous dit que dans certains contrôleurs de mémoire flash, la logique d'usure peut causer des dommages aléatoires au système de fichiers si l'alimentation est interrompue pendant une écriture. Cela peut se manifester, par exemple, par des changements aléatoires au milieu d'un fichier qui n'était même pas ouvert au moment de la coupure de courant. Ainsi, par exemple, un dispositif serait en train d'écrire du contenu dans un fichier MP3 en mémoire flash lorsqu'une perte d'alimentation se produit, et cela pourrait entraîner la corruption d'une base de données SQLite, même si la base de données n'était même pas utilisée au moment de la perte d'alimentation.

4.2. Fausse capacité des clés USB

Il existe de nombreuses clés USB frauduleuses en circulation qui déclarent avoir une capacité élevée (ex : 8GB) mais qui ne sont en réalité capables de stocker qu'une quantité beaucoup plus faible (ex : 1GB). Les tentatives d'écriture sur ces dispositifs entraînent souvent l'écrasement de fichiers sans rapport avec eux. Toute utilisation d'un dispositif de mémoire flash frauduleux peut donc facilement entraîner une corruption de la base de données. Des recherches sur Internet telles que "fake capacity usb" feront apparaître de nombreuses informations inquiétantes sur ce problème.

SQLite est une bibliothèque C qui fonctionne dans le même espace d'adressage que l'application qu'elle sert. Cela signifie que les pointeurs errants, les dépassements de tampon, la corruption du tas, ou d'autres dysfonctionnements de l'application peuvent corrompre la structure de données interne de SQLite et finalement aboutir à un fichier de base de données corrompu. Normalement, ces types de problèmes se manifestent sous forme de segfaults avant que toute corruption de la base de données ne se produise, mais il y a eu des cas où des erreurs de code d'application ont provoqué un dysfonctionnement subtil de SQLite de manière à corrompre le fichier de base de données plutôt que de paniquer.

Le problème de corruption de la mémoire devient plus aigu lorsqu'on utilise des E/S mappées en mémoire. Lorsque tout ou partie du fichier de base de données est mappé dans l'espace d'adressage de l'application, alors un pointeur errant qui écrase n'importe quelle partie de cet espace mappé corrompt immédiatement le fichier de base de données, sans nécessiter que l'application fasse un appel système write() ultérieur.

Parfois, les systèmes d'exploitation présentent un comportement non standard qui peut entraîner des problèmes. Parfois ce comportement non standard est délibéré, et parfois c'est une erreur dans l'implémentation. Mais dans tous les cas, si l'exploitation se comporte différemment de la façon dont SQLite s'attend à ce qu'elle se comporte, la possibilité de corruption de la base de données existe.

6.1. Fils de discussion Linux

Certaines anciennes versions de Linux utilisaient la bibliothèque LinuxThreads pour le support des threads. LinuxThreads est similaire à Pthreads, mais est subtilement différent en ce qui concerne la gestion des verrous consultatifs POSIX. Les versions 2.2.3 à 3.6.23 de SQLite ont reconnu que LinuxThreads était utilisé à l'exécution et ont pris les mesures appropriées pour contourner le comportement non standard de LinuxThreads. Mais la plupart des implémentations modernes de Linux utilisent l'implémentation NPTL de Pthreads, plus récente et correcte. En commençant par SQLite version 3.7.0 (2010-07-21), l'utilisation de NPTL est supposée. Aucune vérification n'est effectuée. Par conséquent, les versions récentes de SQLite vont subtilement dysfonctionner et peuvent corrompre les fichiers de base de données si elles sont utilisées dans une application multithread qui s'exécute sur les anciens systèmes linux qui font usage de LinuxThreads.

6.2. Défaillances de mmap() sur QNX.

Il existe un problème subtil avec mmap() sur QNX tel que faire un deuxième appel mmap() contre un seul descripteur de fichier peut provoquer la mise à zéro de la mémoire obtenue par le premier appel mmap(). SQLite sur unix utilise mmap() pour créer une région de mémoire partagée pour la coordination des transactions en mode WAL, et il appellera mmap() plusieurs fois pour les transactions importantes. Il a été démontré que mmap() de QNX corrompt le fichier de base de données dans ce scénario. Les ingénieurs de QNX sont conscients de ce problème et travaillent sur une solution ; le problème peut avoir déjà été corrigé au moment où vous lisez ceci.

Lors de l'exécution sur QNX, il est recommandé de ne jamais utiliser d'E/S mappées en mémoire. De plus, pour utiliser le mode WAL, il est recommandé que les applications emploient le mode de verrouillage exclusif afin d'utiliser WAL sans mémoire partagée.

6.3. Corruption du système de fichiers

Comme les bases de données SQLite sont des fichiers disques ordinaires, tout dysfonctionnement du système de fichiers peut corrompre la base de données. Les systèmes de fichiers des systèmes d'exploitation modernes sont très fiables, mais des erreurs se produisent tout de même. Par exemple, le 2013-10-01, la base de données SQLite qui contient le fichier Wiki pour Tcl/Tk s'est corrompue quelques jours après que l'ordinateur hôte a été déplacé vers une construction douteuse du noyau (linux) qui avait des problèmes dans la couche du système de fichiers. Dans cet événement, le système de fichiers est finalement devenu si corrompu que la machine était inutilisable, mais le premier symptôme de problème était la base de données SQLite corrompue.

SQLite possède de nombreuses protections intégrées contre la corruption des bases de données. Mais beaucoup de ces protections peuvent être désactivées par des options de configuration. Si les protections sont désactivées, la corruption de la base de données peut se produire.

Voici des exemples de désactivation des mécanismes de protection intégrés de SQLite :

  • Définir PRAGMA synchrone=OFF peut entraîner la corruption de la base de données en cas de crash du système d'exploitation ou de panne de courant, bien que ce paramètre soit à l'abri des dommages dus aux crashs d'applications.

  • Modification de la version_schéma de PRAGMA alors que d'autres connexions à la base de données sont ouvertes.

  • Utilisation de PRAGMA journal_mode=OFF ou PRAGMA journal_mode=MEMORY et prise d'un crash de l'application au milieu d'une transaction d'écriture.

  • Définir PRAGMA writable_schema=ON et modifier ensuite le schéma de la base de données à l'aide d'instructions DML peut rendre la base de données complètement illisible, si cela n'est pas fait avec précaution.

SQLite est très soigneusement testé pour aider à garantir qu'il est aussi exempt de bogues que possible. Parmi les nombreux tests qui sont effectués pour chaque version de SQLite, il y a des tests qui simulent des pannes de courant, des erreurs d'E/S et des erreurs hors mémoire (OOM) et vérifient qu'aucune corruption de la base de données ne se produit pendant l'un de ces événements. SQLite est également éprouvé sur le terrain avec environ deux milliards de déploiements actifs sans aucun problème sérieux.

Néanmoins, aucun logiciel n'est parfait à 100%. Il y a eu quelques bogues historiques dans SQLite (maintenant corrigés) qui pourraient causer une corruption de la base de données. Et il peut y en avoir encore quelques autres qui n'ont pas encore été découverts. En raison des tests approfondis et de l'utilisation répandue de SQLite, les bogues qui entraînent la corruption de la base de données ont tendance à être très obscurs. La probabilité qu'une application rencontre un bogue SQLite est faible. Pour illustrer cela, voici un compte rendu de tous les bogues de corruption de base de données trouvés dans SQLite au cours de la période de quatre ans allant du 2009-04-01 au 2013-04-15. Ce compte-rendu devrait donner au lecteur un sens intuitif des types de bogues dans SQLite qui parviennent à se glisser à travers les procédures de test et à se retrouver dans une version.

8.1. Faux rapports de corruption dus au rétrécissement de la base de données.

Si une base de données est écrite par SQLite version 3.7.0 ou ultérieure, puis réécrite par SQLite version 3.6.23 ou antérieure de manière à faire diminuer la taille du fichier de base de données, alors la prochaine fois que SQLite version 3.7.0 accède au fichier de base de données, il pourrait signaler que le fichier de base de données est corrompu. Cependant, le fichier de base de données n'est pas vraiment corrompu. La version 3.7.0 était simplement trop zélée dans sa détection de corruption.

Le problème a été corrigé le 2011-02-20. Le correctif apparaît d'abord dans SQLite version 3.7.6 (2011-04-12).

8.2. Corruption suite aux commutations entre les modes rollback et WAL.

Le fait de basculer de manière répétée une base de données SQLite dans et hors du mode WAL et d'exécuter la commande VACUUM entre les commutations, dans un processus ou un thread, peut faire en sorte qu'un autre processus ou thread qui a le fichier de base de données ouvert manque le fait que la base de données a changé. Ce deuxième processus ou thread pourrait alors essayer de modifier la base de données en utilisant un cache périmé et provoquer une corruption de la base de données.

Ce problème a été découvert lors de tests internes et n'a jamais été observé dans la nature. Le problème a été corrigé le 2011-01-27 et dans la version 3.7.5.

8.3. Une erreur d'E/S lors de l'obtention d'un verrou entraîne une corruption.

Si le système d'exploitation renvoie une erreur d'E/S lors de la tentative d'obtention d'un certain verrou sur la mémoire partagée en mode WAL, alors SQLite pourrait échouer à réinitialiser son cache, ce qui pourrait entraîner une corruption de la base de données si des écritures ultérieures sont tentées.

Notez que ce problème ne se produit que si la tentative d'acquisition du verrou a donné lieu à une erreur d'E/S. Si le verrou n'est tout simplement pas accordé (parce qu'un autre thread ou processus détient déjà un verrou conflictuel), aucune corruption ne se produira jamais. Nous ne connaissons aucun système d'exploitation qui échoue avec une erreur d'E/S lors de la tentative d'obtenir un verrou de fichier sur la mémoire partagée. Il s'agit donc d'un problème théorique plutôt que d'un problème réel. Il va sans dire que ce problème n'a jamais été observé dans la nature. Le problème a été découvert en effectuant des tests de stress de SQLite dans un harnais de test qui simule des erreurs d'E/S.

Ce problème a été corrigé le 2010-09-20 pour la version 3.7.3 de SQLite.

8.4. Les pages de la base de données fuient de la liste des pages libres.

Lorsque le contenu est supprimé d'une base de données SQLite, les pages qui ne sont plus utilisées sont ajoutées à une liste libre et sont réutilisées pour contenir le contenu ajouté par les insertions ultérieures. Un bogue dans SQLite qui était présent dans les versions 3.6.16 à 3.7.2 pouvait faire disparaître des pages de la liste libre lorsque incremental_vacuum était utilisé. Cela ne causait pas de perte de données. Mais il en résulterait que le fichier de la base de données serait plus grand que nécessaire. Et cela amènerait le pragma integrity_check à signaler des pages manquantes dans la liste libre.

Ce problème a été corrigé le 2010-08-23 pour la version 3.7.2 de SQLite.

8.5. Corruption suite à des écritures alternées de 3.6 et 3.7.

La version 3.7.0 de SQLite a introduit un certain nombre de nouvelles améliorations au format de fichier de base de données SQLite (telles que WAL, mais pas seulement). La version 3.7.0 était une version de test pour ces nouvelles fonctionnalités. Nous nous attendions à trouver des problèmes et n'avons pas été déçus.

Si une base de données était initialement créée en utilisant la version 3.7.0 de SQLite, puis écrite par la version 3.6.23.1 de SQLite de telle sorte que la taille du fichier de base de données a augmenté, puis écrite à nouveau par la version 3.7.0 de SQLite, le fichier de base de données pourrait devenir corrompu.

Ce problème a été corrigé le 2010-08-04 pour la version 3.7.1 de SQLite.

8.6. Race condition dans la récupération sur le système windows.

La version 3.7.16.2 de SQLite corrige une subtile condition de course dans la logique de verrouillage sur les systèmes Windows. Lorsqu'un fichier de base de données a besoin d'être restauré parce que le processus précédent qui y écrit s'est écrasé au milieu d'une transaction et que deux processus ou plus essaient d'ouvrir cette base de données en même temps, alors la condition de course pourrait faire en sorte qu'un de ces processus obtienne une fausse indication que la restauration est déjà terminée, permettant à ce processus de continuer à utiliser le fichier de base de données sans exécuter la restauration en premier. Si ce processus écrit dans le fichier, ce dernier peut être corrompu. Cette condition de course existait apparemment dans toutes les versions précédentes de SQLite for Windows depuis 2004. Mais la course était très serrée. En pratique, vous avez besoin d'une machine rapide à plusieurs cœurs dans laquelle vous lancez deux processus pour exécuter la récupération au même moment sur deux cœurs distincts. Ce défaut concernait uniquement les systèmes Windows et n'affectait pas l'interface posix OS.