Menu

Erreurs SQLite courantes : database is locked, readonly...

Les erreurs SQLite que vous croiserez vraiment en production — database is locked, readonly database, disk image malformed, contraintes — et comment les régler.

Cette page contient des éditeurs exécutables — modifiez, exécutez et voyez la sortie instantanément.

Les erreurs SQLite, c'est juste le moteur qui vous parle

Les messages d'erreur de SQLite sont courts et parfois un peu obscurs, mais ils renvoient toujours à un petit nombre de problèmes sous-jacents. En production, la plupart des erreurs SQLite courantes se rangent dans cinq grandes catégories : verrouillage, permissions, corruption, désaccord de schéma et violations de contraintes. Cette page passe chaque cas en revue : ce qui le déclenche, ce qu'il signifie vraiment, et comment s'en sortir.

Les messages textuels viennent toujours avec un code numérique (et les codes étendus sont encore plus précis). Vous croiserez les deux formes dans vos logs :

Erreur : la base de données est verrouillée   -- code 5 (SQLITE_BUSY)
Erreur : impossible d'ouvrir la base de données -- code 14 (SQLITE_CANTOPEN)
Erreur : tentative d'écriture en lecture seule -- code 8 (SQLITE_READONLY)
Erreur : l'image disque de la base est corrompue -- code 11 (SQLITE_CORRUPT)

Connaître le code aide vraiment quand on cherche sur Google — SQLITE_BUSY donne de bien meilleurs résultats que le message en anglais brut.

database is locked (SQLITE_BUSY) : l'erreur sqlite la plus fréquente

C'est l'erreur SQLite numéro un dès qu'une application écrit depuis plusieurs endroits à la fois. SQLite sérialise les écritures : une seule connexion peut détenir le verrou d'écriture à un instant donné. Si un deuxième écrivain n'arrive pas à obtenir le verrou avant l'expiration du busy timeout, vous tombez sur cette erreur.

Voici trois solutions, classées par ordre d'efficacité :

Le mode WAL suffit, à lui seul, à régler les soucis de verrouillage dans la plupart des cas. Le busy timeout, lui, sert de filet de sécurité quand il y a vraiment de la contention en écriture. Au-delà des réglages, relisez votre code : une transaction laissée ouverte pendant que le programme fait des I/O réseau, c'est un verrou maintenu tout ce temps-là. Gardez vos transactions courtes et faites un COMMIT (ou un ROLLBACK) dès que le travail est terminé.

unable to open database file (SQLITE_CANTOPEN)

SQLite a tenté d'ouvrir le fichier et le système d'exploitation a répondu non. Dans 95 % des cas, le problème vient du chemin du fichier ou de son dossier :

-- Choses à vérifier :
-- 1. Le chemin existe-t-il ?            ls -l /chemin/vers/db.sqlite
-- 2. Le répertoire parent existe-t-il ? SQLite crée le fichier
--    mais pas le répertoire qui le contient.
-- 3. L'utilisateur qui exécute votre processus dispose-t-il des permissions
--    de lecture+écriture sur le répertoire (et pas seulement sur le fichier) ?
-- 4. Le volume est-il monté, non plein et non en lecture seule ?

Cas subtil : SQLite a besoin de créer des fichiers annexes (-journal, -wal, -shm) à côté de la base. Si le fichier lui-même est accessible en écriture mais pas son dossier, l'ouverture passe sans souci... et c'est l'écriture qui plante. Donnez toujours les droits d'écriture au niveau du répertoire.

attempt to write a readonly database (SQLITE_READONLY)

Proche cousin du précédent. Le fichier s'ouvre sans problème, mais impossible d'écrire dedans. Les causes, classées par fréquence :

  • L'utilisateur système n'a pas les droits d'écriture sur le fichier ou son dossier.
  • La connexion a été ouverte en lecture seule (SQLITE_OPEN_READONLY, ou mode=ro dans l'URI).
  • Le volume est monté en lecture seule (cas fréquent avec les bind mounts Docker et certains systèmes de fichiers cloud).
  • La base se trouve sur un système de fichiers réseau qui ne gère pas le verrouillage requis par SQLite.

Corrigez les permissions ou remontez le volume. Si vous êtes sous Docker, vérifiez que le bind mount n'est pas en :ro et que l'utilisateur du conteneur est bien propriétaire du dossier.

database disk image is malformed (SQLITE_CORRUPT)

Les octets du fichier ne correspondent plus au format attendu par SQLite. Dans la pratique, les causes sont presque toujours liées à l'environnement : un processus tué en plein écriture sur un système de fichiers sans fsync correct, une copie de la base pendant qu'un writer était actif, une panne matérielle, ou encore une synchronisation du fichier via Dropbox ou iCloud.

Commencez par confirmer les dégâts :

Si integrity_check renvoie ok, c'est que la base est saine et que l'erreur vient d'ailleurs (souvent une connexion qui traîne). En revanche, si vous obtenez une liste de problèmes, il va falloir passer par une récupération.

La méthode la plus propre, c'est la commande .recover du CLI, qui extrait tout ce qu'elle peut dans une nouvelle base :

sqlite3 corrupt.db ".recover" | sqlite3 recovered.db
sqlite3 recovered.db "PRAGMA integrity_check;"

Si vous avez une sauvegarde récente, restaurez-la plutôt — c'est plus rapide et ça évite le flou du « on a récupéré presque tout ». Jetez un œil à la page sur la sauvegarde et la restauration pour savoir comment copier proprement une base en cours d'utilisation (indice : surtout pas avec cp).

no such table et no such column

Ces messages veulent dire exactement ce qu'ils annoncent, mais la cause se résume presque toujours à deux scénarios : soit vous êtes connecté à une base différente de celle que vous croyez, soit une migration n'est jamais passée.

Vérifiez la chaîne de connexion de votre application : les chemins relatifs sont résolus par rapport au répertoire de travail courant, qui change selon que vous lancez depuis votre terminal, votre IDE ou votre process en production. Une base en mémoire (:memory:) repart de zéro à chaque exécution — un piège classique quand on s'attend à ce que les données persistent.

Le quoting des identifiants compte aussi. Sans guillemets, les noms sont insensibles à la casse, mais "User" et "user" sont bel et bien deux identifiants distincts. Si vous avez créé une table avec des guillemets autour du nom, il faudra continuer à la citer ainsi partout.

Violations de contraintes

SQLite refuse toute écriture qui briserait une contrainte. Le message d'erreur t'indique précisément laquelle :

Chaque échec correspond à un code différent en interne (SQLITE_CONSTRAINT_UNIQUE, SQLITE_CONSTRAINT_CHECK, SQLITE_CONSTRAINT_NOTNULL). Le correctif se situe presque toujours côté applicatif : validez les données avant de les écrire, ou utilisez INSERT ... ON CONFLICT pour gérer les doublons de manière explicite.

L'erreur FOREIGN KEY constraint failed mérite un mot à part : dans SQLite, les clés étrangères sont désactivées par défaut. Si vous ne les activez pas, les références invalides passent en silence, puis tout casse plus tard quand vous finissez par activer la contrainte. Pensez à définir le pragma sur chaque connexion :

cannot start a transaction within a transaction

Vous avez lancé un BEGIN alors qu'une transaction était déjà ouverte. SQLite ne gère pas les transactions imbriquées, mais vous pouvez obtenir le même résultat avec des savepoints imbriqués :

Si votre ORM ou votre framework gère les transactions tout seul, il y a de fortes chances que vous en ayez démarré une en double. Vérifiez si l'autocommit est activé, et si votre pool de connexions ne te refile pas une connexion sur laquelle une transaction est déjà ouverte.

disk I/O error (SQLITE_IOERR)

L'OS a refusé une lecture ou une écriture. Disque plein, hoquet d'un système de fichiers réseau, ou alors le fichier a été supprimé sous le nez de SQLite. Le premier réflexe : df -h. Le second : regarder si la base est posée sur un truc instable comme NFS ou un dossier synchronisé dans le cloud — SQLite part du principe qu'il tourne sur un système de fichiers POSIX local avec un fsync qui fonctionne vraiment. Si vous ne pouvez pas la déplacer, dites-vous que le risque de corruption grimpe.

syntax error near "..."

Le parseur de SQLite te précise quel token l'a fait tiquer. Le bug est en général trois lignes plus haut que ce que pointe l'erreur : une virgule oubliée, un identifiant non quoté qui entre en collision avec un mot-clé, ou une chaîne avec des apostrophes mal échappées ('it''s', et non 'it's').

Préférez le binding de paramètres (avec des ?) plutôt que de construire vos requêtes SQL par concaténation de chaînes : vous évitez d'un seul coup toute une catégorie d'erreurs de syntaxe ainsi que les injections SQL.

Checklist de diagnostic

Quand ça casse en prod, la séquence ci-dessous couvre la plupart des cas en moins d'une minute :

Cinq pragmas, cinq réponses. En les combinant avec le code d'erreur de la requête qui a échoué, vous saurez dans quelle catégorie ranger le problème et quelle page de doc ouvrir ensuite.

Conclusion du parcours

Voilà, le tour est bouclé. Vous êtes parti de CREATE TABLE et vous avez enchaîné les jointures, les index, les transactions, le mode WAL, les sauvegardes, et maintenant les modes de défaillance qui surgissent quand SQLite rencontre la vraie vie. Les bonnes pratiques reviennent toujours : des transactions courtes, les clés étrangères activées, le mode WAL, des sauvegardes régulières, et un respect sain pour PRAGMA integrity_check. Gardez ces réflexes et SQLite tournera tranquillement pendant des années.

Questions fréquentes

Pourquoi SQLite renvoie-t-il « database is locked » ?

Une autre connexion détient un verrou en écriture et la vôtre a expiré en attendant. Les correctifs habituels : activer le mode WAL avec PRAGMA journal_mode=WAL pour que les lecteurs ne bloquent plus les écrivains, augmenter le timeout via PRAGMA busy_timeout = 5000, et veiller à committer vos transactions rapidement au lieu de les laisser ouvertes.

Comment corriger « attempt to write a readonly database » dans SQLite ?

Dans 99 % des cas, c'est un souci de permissions du système de fichiers, pas de SQLite. L'utilisateur qui exécute votre process doit avoir les droits d'écriture à la fois sur le fichier de base et sur le dossier qui le contient (SQLite y crée des fichiers compagnons -journal ou -wal). Vérifiez le propriétaire, les droits, et que le volume n'est pas monté en lecture seule.

Que signifie « database disk image is malformed » ?

SQLite a lu des octets qui ne correspondent pas au format attendu — le plus souvent une corruption due à un process tué brutalement, un disque défectueux, ou une copie du fichier pendant qu'il était ouvert. Lancez PRAGMA integrity_check pour confirmer, puis utilisez .recover dans la CLI pour extraire ce qui peut l'être vers une nouvelle base. Si vous avez une sauvegarde récente, restaurer sera plus rapide.

Pourquoi ai-je « no such table » ou « no such column » ?

Soit vous êtes connecté à un autre fichier que celui que vous croyez, soit une migration n'est pas passée. Vérifiez avec PRAGMA database_list le chemin réellement ouvert par SQLite, puis .schema nomdetable pour voir les vraies colonnes. Les fautes de frappe et les différences de casse sont aussi classiques : SQLite ignore la casse pour les identifiants non quotés, mais pas pour ceux entre guillemets.

Coddy programming languages illustration

Apprendre à coder avec Coddy

COMMENCER