Menu

Errori comuni in SQLite: database locked, readonly, malformed e altri

Gli errori SQLite che incontrerai davvero in produzione: database is locked, readonly database, disk image malformed, violazioni dei vincoli, e come risolverli.

Questa pagina include editor eseguibili: modifica, esegui e vedi subito l'output.

Gli errori sono solo SQLite che ti dice qualcosa

I messaggi di errore di SQLite sono brevi e a volte criptici, ma corrispondono a un piccolo insieme di problemi di fondo. Quasi tutto ciò che incontrerai in produzione rientra in cinque categorie: lock, permessi, corruzione, schema non allineato e violazioni dei vincoli. Questa pagina le passa in rassegna una per una: cosa le provoca, cosa significano davvero e come risolverle.

I messaggi di errore arrivano accompagnati da codici numerici (i codici estesi sono ancora più specifici). Nei log vedrai entrambe le forme:

Error: database is locked          -- codice 5 (SQLITE_BUSY)
Error: unable to open database     -- codice 14 (SQLITE_CANTOPEN)
Error: attempt to write a readonly -- codice 8 (SQLITE_READONLY)
Error: database disk image is      -- codice 11 (SQLITE_CORRUPT)

Conoscere il codice aiuta nelle ricerche: SQLITE_BUSY dà risultati molto migliori del semplice messaggio in inglese.

database is locked (SQLITE_BUSY)

È l'errore SQLite più comune in qualsiasi applicazione che scrive da più punti. SQLite serializza le scritture: una sola connessione alla volta può tenere il lock di scrittura. Se un secondo processo di scrittura non ottiene il lock entro il busy timeout, ricevi questo errore.

Tre soluzioni, in ordine di impatto:

La sola modalità WAL risolve il problema dei lock per la maggior parte dei carichi di lavoro. Il busy timeout è la tua rete di sicurezza per i veri conflitti di scrittura. Oltre alle impostazioni, controlla il codice: una transazione lasciata aperta mentre il programma fa I/O di rete terrà il lock per tutto quel tempo. Tieni le transazioni brevi e fai COMMIT (o ROLLBACK) appena il lavoro è finito.

unable to open database file (SQLITE_CANTOPEN)

SQLite ha provato ad aprire il file e il sistema operativo ha detto no. Nel 95% dei casi il problema è il percorso del file o la sua directory:

-- Cose da controllare:
-- 1. Il percorso esiste?              ls -l /path/to/db.sqlite
-- 2. La directory padre esiste?       SQLite crea il file
--    ma non la directory che lo contiene.
-- 3. L'utente che esegue il tuo processo ha i permessi di
--    lettura+scrittura sulla directory (non solo sul file)?
-- 4. Il volume è montato, non è pieno e non è in sola lettura?

Un caso sottile: SQLite deve creare file accessori (-journal, -wal, -shm) accanto al database. Se il file è scrivibile ma la directory no, l'apertura riesce e le scritture falliscono. Concedi sempre il permesso di scrittura a livello di directory.

attempt to write a readonly database (SQLITE_READONLY)

Parente stretto del precedente. Il file si è aperto senza problemi, ma le scritture falliscono. Le cause, in ordine di frequenza:

  • L'utente del sistema operativo non ha il permesso di scrittura sul file o sulla sua directory.
  • La connessione è stata aperta con un flag di sola lettura (SQLITE_OPEN_READONLY, oppure mode=ro in un URI).
  • Il volume è montato in sola lettura (frequente con i bind mount di Docker e alcuni filesystem cloud).
  • Il database si trova su un filesystem di rete che non supporta i lock di cui SQLite ha bisogno.

Correggi i permessi o rimonta il volume. Se sei in Docker, assicurati che il bind mount non sia :ro e che l'utente del container sia proprietario della directory.

database disk image is malformed (SQLITE_CORRUPT)

I byte del file non corrispondono più al formato di SQLite. Le cause reali di solito dipendono dall'ambiente: processi terminati durante una scrittura su filesystem senza un fsync affidabile, copia del database mentre qualcuno scriveva, guasti hardware, oppure sincronizzazione del file tramite Dropbox/iCloud.

Per prima cosa, conferma il danno:

Se integrity_check restituisce ok, il database sta bene e l'errore è arrivato da un'altra parte (spesso una connessione obsoleta). Se restituisce un elenco di problemi, devi recuperare i dati.

Il percorso di recupero più pulito usa il comando .recover della CLI, che estrae tutti i dati possibili in un database nuovo:

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

Se hai un backup recente, ripristina quello: è più veloce ed evita l'ambiguità del "abbiamo recuperato quasi tutto". Consulta la pagina su backup e ripristino per il modo giusto di copiare un database in uso (suggerimento: non con cp).

no such table e no such column

Significano esattamente quello che dicono, ma la causa di solito è una di due: la connessione punta a un database diverso da quello che pensi, oppure una migrazione non è stata eseguita.

Controlla la stringa di connessione della tua applicazione: i percorsi relativi vengono risolti rispetto alla directory di lavoro corrente, che cambia tra il tuo terminale, il tuo IDE e il processo in produzione. Un database in memoria (:memory:) è nuovo ogni volta, e questo spiazza chi si aspetta che i dati restino.

Conta anche come metti tra virgolette gli identificatori. I nomi senza virgolette non distinguono maiuscole e minuscole, ma "User" e "user" sono identificatori diversi. Se hai creato una tabella con il nome tra virgolette, devi continuare a metterlo tra virgolette.

Violazioni dei vincoli

SQLite rifiuta le scritture che violerebbero un vincolo. Il messaggio di errore indica quale:

Sotto il cofano ogni errore ha un codice diverso (SQLITE_CONSTRAINT_UNIQUE, SQLITE_CONSTRAINT_CHECK, SQLITE_CONSTRAINT_NOTNULL). La soluzione sta quasi sempre nel livello applicativo: valida l'input prima di scrivere, oppure usa INSERT ... ON CONFLICT per gestire i duplicati in modo intenzionale.

FOREIGN KEY constraint failed merita una nota a parte: in SQLite le chiavi esterne sono disattivate di default. Se non le attivi, i riferimenti non validi entrano senza avvisi e poi falliscono più tardi, quando finalmente attivi il controllo. Imposta il pragma su ogni connessione:

cannot start a transaction within a transaction

Hai chiamato BEGIN mentre una transazione era già aperta. SQLite non consente transazioni annidate, ma consente savepoint annidati, che ottengono lo stesso effetto:

Se è il tuo ORM o framework a gestire le transazioni, probabilmente gli hai detto di avviarne una due volte. Controlla se l'autocommit è attivo e se il tuo pool di connessioni sta riutilizzando una connessione che ha già una transazione aperta.

disk I/O error (SQLITE_IOERR)

Il sistema operativo ha rifiutato una lettura o una scrittura. Disco pieno, un intoppo del filesystem di rete, oppure il file è stato cancellato mentre SQLite lo usava. La prima cosa da controllare è df -h. La seconda è se il database vive su qualcosa di instabile come NFS o una cartella sincronizzata nel cloud: SQLite presuppone un filesystem POSIX locale con un fsync funzionante. Se non puoi spostarlo, accetta che il rischio di corruzione aumenti.

syntax error near "..."

Il parser di SQLite ti dice quale token l'ha confuso. Di solito la correzione va fatta tre righe prima del punto indicato dall'errore: una virgola mancante, un identificatore senza virgolette che coincide con una parola chiave, oppure una stringa con apici singoli da fare in escape ('it''s', non 'it's').

Usa il binding dei parametri (placeholder ?) per l'input degli utenti invece di costruire l'SQL concatenando stringhe: eviterai in un colpo solo un'intera categoria di errori di sintassi e la SQL injection.

Una checklist diagnostica

Quando qualcosa si rompe in produzione, questa sequenza copre la maggior parte dei casi in meno di un minuto:

Cinque pragma, cinque risposte. Insieme al codice di errore della query fallita, saprai a quale categoria appartiene il problema e quale pagina della documentazione aprire dopo.

Chiudere il percorso

Il giro è finito. Il percorso ti ha portato da CREATE TABLE a join, indici, transazioni, modalità WAL, backup e ora ai modi in cui le cose si rompono quando SQLite incontra il mondo reale. Gli schemi si ripetono: transazioni brevi, chiavi esterne attive, modalità WAL, backup regolari e un sano rispetto per PRAGMA integrity_check. Mantieni queste abitudini e SQLite funzionerà in silenzio per anni.

Domande frequenti

Perché SQLite dice 'database is locked'?

Un'altra connessione tiene un lock di scrittura e la tua è andata in timeout mentre aspettava. Le soluzioni abituali sono attivare la modalità WAL con PRAGMA journal_mode=WAL, così chi legge non blocca chi scrive, aumentare il busy timeout con PRAGMA busy_timeout = 5000 e assicurarti di fare il commit delle transazioni subito invece di lasciarle aperte.

Come risolvo 'attempt to write a readonly database' in SQLite?

Quasi sempre è un problema di permessi del filesystem, non di SQLite. L'utente del sistema operativo che esegue il tuo processo deve avere accesso in scrittura sia al file del database sia alla directory che lo contiene (SQLite ci crea i file accessori -journal o -wal). Controlla proprietario, permessi e che il volume non sia montato in sola lettura.

Cosa significa 'database disk image is malformed'?

SQLite ha letto byte che non corrispondono al formato che si aspetta, di solito per una corruzione causata da processi terminati bruscamente, dischi difettosi o dalla copia del file mentre era aperto. Esegui PRAGMA integrity_check per confermarlo, poi recupera con .recover nella CLI per esportare ciò che si può salvare in un database nuovo. Se hai un backup, ripristinarlo è più veloce.

Perché ricevo 'no such table' o 'no such column'?

La tua connessione punta a un file di database diverso da quello che pensi, oppure una migrazione non è stata eseguita. Controlla PRAGMA database_list per vedere il percorso del file che SQLite ha effettivamente aperto, e .schema tablename per vedere le colonne reali. Anche errori di battitura e differenze di maiuscole negli identificatori sono frequenti: SQLite non distingue maiuscole e minuscole nei nomi senza virgolette, ma le distingue in quelli tra virgolette.

Illustrazione dei linguaggi di programmazione di Coddy

Impara a programmare con Coddy

INIZIA