Quante volte hai sentito dire: “SQLite? Carino, ma è un giocattolo. Per le cose serie serve un vero database server”? È uno dei miti più duri a morire nel mondo dello sviluppo.
La verità è che la differenza tra un file .db che si corrompe sotto carico e il motore dati che gestisce miliardi di chat su WhatsApp non è il database in sé, ma come viene configurato.
Questo articolo parla proprio di quella configurazione: i comandi PRAGMA. Non sono SQL standard; sono la sala macchine di SQLite. Sono le leve che permettono di attivare i vincoli di integrità (spesso spenti di default), di cambiare il motore di scrittura per gestire migliaia di accessi concorrenti e di dire al database quanta RAM usare.
Se hai sempre usato SQLite “così com’è”, preparati a scoprire che stavi guidando una supercar in prima marcia.
Cos’è un PRAGMA in SQLite?
La parola chiave PRAGMA deriva da “Pragmatic instruction” (istruzione pragmatica). È un’estensione proprietaria del linguaggio SQL che consente di leggere o modificare il comportamento interno del motore SQLite, senza dover ricompilare né riavviare nulla.
Non è parte dello standard SQL ANSI/ISO: è una funzione “speciale” esclusiva di SQLite.
In pratica, un PRAGMA si esegue così:
PRAGMA nome_pragma; -- legge il valore corrente
PRAGMA nome_pragma = valore; -- imposta un nuovo valore
PRAGMA nome_pragma(parametro); -- esegue un’azione
Esempio lampo:
PRAGMA foreign_keys; -- restituisce 0 (spento) o 1 (acceso)
PRAGMA foreign_keys = ON; -- attiva i vincoli referenziali
A Cosa Servono Davvero i PRAGMA?
Immagina SQLite come una Ferrari da 300 cavalli, ma con i freni a mano tirati di default. I PRAGMA sono la leva che sblocca il vero potenziale del motore, migliorando prestazioni, sicurezza e affidabilità.
| Categoria | Cosa Controllano i PRAGMA | Senza PRAGMA (default) | Con PRAGMA Ottimizzati |
| Performance | Journal, cache, memory map | journal_mode=DELETE, cache 2 MB |
WAL + cache 64-256 MB → 10-100x più veloce |
| Integrità dati | FOREIGN KEY, STRICT |
FK disattive, tipi flessibili | FK attive, tipi rigidi → zero dati corrotti |
| Sicurezza | Schema trusted | Trigger malevoli possibili | trusted_schema=OFF → maggiore protezione |
| Crash recovery | Synchronous, WAL checkpoint | Possibile perdita dati recenti | synchronous=NORMAL + WAL → zero corruzione |
| Manutenzione | VACUUM, optimize |
DB frammentato, query lente | Script notturno → DB sempre fluido |
| Debug | table_info, index_list |
Diagnosi complesse | Analisi immediata di tabelle e indici |
In una frase:
I PRAGMA sono l’80% della differenza tra un DB SQLite “giocattolo” e un DB usato da WhatsApp, Spotify, Chrome, Dropbox o Tesla.
Dove e Quando Usare i PRAGMA
- All’apertura di ogni connessione (fondamentale!)
PRAGMA journal_mode = WAL; PRAGMA foreign_keys = ON; PRAGMA cache_size = -64000; -- 64 MiB PRAGMA busy_timeout = 5000; - Una sola volta per database (al primo avvio/creazione)
PRAGMA encoding = 'UTF-8'; PRAGMA application_id = 12345; PRAGMA user_version = 1; - Durante la manutenzione periodica (es. script notturno)
PRAGMA optimize; PRAGMA wal_checkpoint(TRUNCATE); PRAGMA incremental_vacuum(1000); - In fase di debug
PRAGMA integrity_check; PRAGMA foreign_key_check; PRAGMA index_list(utenti);
Esempio Pratico: Prima e Dopo i PRAGMA
❌ Senza PRAGMA (default SQLite)
PRAGMA journal_mode; -- DELETE
PRAGMA foreign_keys; -- OFF
PRAGMA cache_size; -- 2000 (pagine, ~8 MB)
PRAGMA synchronous; -- FULL
- Risultato: Scrittura di 100 record → 1.2 secondi. Due thread si bloccano a vicenda. Un
ON DELETE CASCADEviene ignorato.
✅ Con PRAGMA ottimizzati
PRAGMA journal_mode = WAL;
PRAGMA foreign_keys = ON;
PRAGMA cache_size = -128000; -- 128 MiB
PRAGMA synchronous = NORMAL;
PRAGMA mmap_size = 536870912; -- 512 MB
- Risultato: Scrittura di 100 record → 0.08 secondi (circa 15x più veloce). 10 thread scrivono in parallelo. Un
ON DELETE CASCADEfunziona perfettamente.
🚀 I 15 PRAGMA Indispensabili del 2025 (Divisi per Caso d’Uso)
Non tutti i PRAGMA servono sempre. La configurazione ottimale per un’app mobile è diversa da quella per un server web. Ecco le configurazioni pronte da copiare.
PRAGMA Fondamentali (La Base per Tutti)
Questi tre definiscono l’integrità e il formato del tuo database.
PRAGMA foreign_keys = ON;- Cosa fa: Attiva i vincoli di chiave esterna.
- Perché: Senza questo, i tuoi
REFERENCESeON DELETE CASCADEsono inutili. È spento di default per retrocompatibilità.
PRAGMA encoding = 'UTF-8';- Cosa fa: Imposta la codifica del testo a UTF-8.
- Perché: Va impostato una sola volta alla creazione del DB per garantire la compatibilità dei caratteri.
PRAGMA strict = ON;- Cosa fa: Attiva la modalità “rigida” per le tabelle create con la parola chiave
STRICT. - Perché: Impedisce a SQLite di accettare dati di tipo errato (es. salvare “ciao” in una colonna
INTEGER). Fondamentale per la coerenza dei dati.
- Cosa fa: Attiva la modalità “rigida” per le tabelle create con la parola chiave
Scenario 1: App Server / API Web (Alta Concorrenza)
Obiettivo: Massima velocità di scrittura e lettura parallela.
PRAGMA journal_mode = WAL;(Write-Ahead Logging)- Cosa fa: Cambia il meccanismo di transazione. Invece di bloccare il DB, le modifiche vengono scritte in un file
.walseparato. - Perché: È la chiave per la concorrenza. Permette a più client di leggere mentre un client sta scrivendo. È il “turbo” di SQLite.
- Cosa fa: Cambia il meccanismo di transazione. Invece di bloccare il DB, le modifiche vengono scritte in un file
PRAGMA synchronous = NORMAL;- Cosa fa: Rilassa i controlli di scrittura sul disco.
- Perché: In modalità
WAL,NORMALè sicuro: garantisce che i dati non si corrompano, ma un crash di sistema potrebbe far perdere l’ultima transazione. Un compromesso performance/sicurezza quasi sempre accettabile.
PRAGMA cache_size = -128000;(128 MiB)- Cosa fa: Assegna 128 MiB di RAM a questa connessione per la cache.
- Perché: Mantenere le pagine del DB usate di frequente in RAM riduce drasticamente gli accessi al disco. Il numero negativo imposta la cache in KiB.
PRAGMA busy_timeout = 5000;- Cosa fa: Se il DB è bloccato, attende 5000ms (5s) prima di dare errore.
- Perché: Vitale in ambienti concorrenti. Dà il tempo a un’altra transazione di finire, invece di fallire immediatamente.
PRAGMA temp_store = MEMORY;- Cosa fa: Dice a SQLite di creare tabelle temporanee (per query complesse,
ORDER BY, ecc.) in RAM invece che su file.
- Cosa fa: Dice a SQLite di creare tabelle temporanee (per query complesse,
PRAGMA mmap_size = 268435456;(256 MB)- Cosa fa: Mappa i primi 256 MB del file del database direttamente in memoria.
- Perché: Accelera enormemente le operazioni di lettura su database di grandi dimensioni.
Script Copia-Incolla (Server/Web):
-- Eseguire ad ogni connessione
PRAGMA journal_mode = WAL;
PRAGMA synchronous = NORMAL;
PRAGMA foreign_keys = ON;
PRAGMA strict = ON;
PRAGMA busy_timeout = 5000;
PRAGMA cache_size = -128000; -- 128 MiB
PRAGMA temp_store = MEMORY;
-- Eseguire una sola volta (o se si ha accesso esclusivo)
PRAGMA mmap_size = 268435456; -- 256 MiB
Scenario 2: App Mobile / Desktop (Equilibrio e Robustezza)
Obiettivo: Ottime performance, ma con un occhio al consumo di risorse e alla robustezza delle migrazioni.
- Si usano i PRAGMA dello Scenario 1, ma con valori più conservativi (es.
cache_sizepiù piccolo). - Si aggiungono PRAGMA di “identità” per gestire le versioni.
PRAGMA cache_size = -64000;(64 MiB)- Cosa fa: Un valore di cache più bilanciato per un dispositivo mobile o un PC.
PRAGMA application_id = 1234;- Cosa fa: Imposta un ID univoco per la tua app nel file DB.
- Perché: Utile per verificare che si stia aprendo il file corretto.
PRAGMA user_version = 2;- Cosa fa: Memorizza un numero di versione dello schema.
- Perché: È il modo standard per gestire le migrazioni. La tua app legge
PRAGMA user_version;e, se è< 2, esegue gli script di aggiornamento e poi impostaPRAGMA user_version = 2;.
Script Copia-Incolla (Mobile/Desktop):
-- Eseguire ad ogni connessione
PRAGMA journal_mode = WAL;
PRAGMA synchronous = NORMAL;
PRAGMA foreign_keys = ON;
PRAGMA strict = ON;
PRAGMA cache_size = -64000; -- 64 MiB
-- Da usare per le migrazioni (logica applicativa)
-- Leggi PRAGMA user_version;
-- ...esegui migrazioni...
-- PRAGMA user_version = NUOVA_VERSIONE;
Scenario 3: Embedded / IoT / WASM (Sicurezza Assoluta)
Obiettivo: Integrità dei dati sopra ogni cosa. Zero corruzione anche in caso di spegnimento improvviso.
- Qui,
WALè opzionale. Per la massima sicurezza, si torna al journal classico ma con sincronizzazione totale.
PRAGMA journal_mode = TRUNCATE;(oDELETE)- Cosa fa: Modalità di journal classica.
- Perché: È più facile da gestire su filesystem semplici (IoT/Embedded).
PRAGMA synchronous = FULL;(oEXTRA)- Cosa fa: Forza il sistema operativo a confermare che ogni scrittura sia fisicamente sul disco prima di procedere.
- Perché: È lento, ma garantisce che, in caso di crash o blackout, il DB sia sempre in uno stato coerente.
PRAGMA trusted_schema = OFF;- Cosa fa: Impedisce a trigger e viste contenuti nel database di eseguire funzioni SQL.
- Perché: Fondamentale in ambienti (come WASM o plugin) dove il file DB potrebbe provenire da una fonte non fidata. Previene attacchi di SQL injection tramite lo schema.
Script Copia-Incolla (Sicurezza Massima / Embedded):
-- Eseguire ad ogni connessione
PRAGMA journal_mode = TRUNCATE;
PRAGMA synchronous = FULL;
PRAGMA foreign_keys = ON;
PRAGMA strict = ON;
PRAGMA trusted_schema = OFF;
Scenario 4: Manutenzione Periodica (Script Notturno)
Obiettivo: Mantenere il DB compatto, veloce e analizzato.
PRAGMA optimize;- Cosa fa: Raccoglie statistiche aggiornate sugli indici per aiutare il query planner.
PRAGMA wal_checkpoint(TRUNCATE);- Cosa fa: (Solo se usi WAL) Forza la scrittura dei dati dal file
.walal file.dbprincipale e rimpicciolisce il file.wal.
- Cosa fa: (Solo se usi WAL) Forza la scrittura dei dati dal file
PRAGMA incremental_vacuum(1000);- Cosa fa: Rimuove 1000 pagine vuote dal file DB.
- Perché: Meglio di
VACUUM(che blocca tutto), permette di recuperare spazio un po’ alla volta.
Script Copia-Incolla (Manutenzione):
PRAGMA optimize;
PRAGMA wal_checkpoint(TRUNCATE);
PRAGMA incremental_vacuum(1000);
PRAGMA integrity_check;
Esempi Pratici di Utilizzo dei PRAGMA per Risolvere Problemi Reali
SQLite è semplice… fino a quando non diventa grande. Ecco i problemi comuni e come i PRAGMA li risolvono.
1. Problema: Vincoli FOREIGN KEY Ignorati
- Sintomo: Hai definito relazioni
ON DELETE CASCADE, ma quando elimini un record “padre”, i “figli” restano, creando dati orfani. - Soluzione:
PRAGMA foreign_keys = ON; - Spiegazione: SQLite, per impostazione predefinita, non applica vincoli FK. Devi attivarli esplicitamente per ogni singola connessione.
2. Problema: Database Lento con Molte Scritture (Collo di Bottiglia)
- Sintomo: In un’app con molte scritture, SQLite diventa lento. Le operazioni si bloccano a vicenda (
database is locked). - Soluzione:
PRAGMA journal_mode = WAL; PRAGMA synchronous = NORMAL; - Spiegazione: WAL trasforma la scrittura. Le letture non bloccano le scritture e le scritture non bloccano le letture.
synchronous=NORMALriduce il flush su disco, rendendo le scritture esponenzialmente più veloci. - Benchmark (esempio):
- 100.000 INSERT (modalità DELETE): 12,4 s
- 100.000 INSERT (modalità WAL): 0,9 s
3. Problema: Errori Logici Dovuti al “Type Affinity”
- Sintomo: Inserisci la stringa “20” in una colonna
INTEGERe SQLite la accetta. Poi fai una queryWHERE eta > 100e ottieni risultati strani, perché “20” viene confrontato come testo. - Soluzione:
PRAGMA strict = ON;(e usaCREATE TABLE ... STRICT;) - Spiegazione: Attivando la modalità
STRICT, SQLite rifiuta qualsiasi valore non compatibile con il tipo dichiarato, generando un errore immediato e proteggendoti da bug logici.
4. Problema: Perdita di Dati Dopo Crash o Spegnimento Improvviso
- Sintomo: Dopo una chiusura forzata, alcune transazioni recenti non risultano salvate o, nel peggiore dei casi, il database è corrotto.
- Soluzione:
PRAGMA synchronous = FULL;(oEXTRA) - Spiegazione: Questa è l’impostazione “paranoica”. Forza SQLite a scrivere e verificare ogni transazione sul disco fisico. È più lenta, ma è la garanzia assoluta contro la corruzione.
5. Problema: Debugging e Ispezione Strutturale
- Sintomo: Non sai perché una query è lenta o se un indice viene effettivamente usato.
- Soluzione:
PRAGMA table_info(utenti); PRAGMA index_list(utenti); PRAGMA foreign_key_check; - Spiegazione: Questi PRAGMA diagnostici sono i tuoi migliori amici.
table_infoelenca colonne e tipi.index_listmostra gli indici.foreign_key_checkcerca nel DB eventuali violazioni di integrità già presenti.
Esempio Pratico Python
Ecco un esempio pratico in Python che dimostra la differenza critica tra una connessione SQLite standard e una connessione ottimizzata con i PRAGMA.
Ci concentreremo sulla differenza più facile da dimostrare: l’attivazione di PRAGMA foreign_keys = ON;.
Lo Scenario: Autori e Libri
Vogliamo creare un database per una libreria. Abbiamo due tabelle:
autori(conidenome)libri(conid,titoloeautore_idche è una chiave esterna riferita aautori.id)
Vogliamo che il database impedisca l’inserimento di un libro se il suo autore_id non esiste nella tabella autori.
Questo script esegue due test sullo stesso file:
- Test 1 (Default): Prova a inserire un libro “orfano”. Vedrai che SQLite, di default, lo permette (e questo è un grosso problema).
- Test 2 (Con PRAGMA): Applica i PRAGMA corretti e prova a fare lo stesso inserimento. Vedrai che il database lo bloccherà con un errore.
import sqlite3
import os
DB_FILE = "database_demo.db"
# Script di setup con i PRAGMA "magici"
PRAGMA_SCRIPT = """
PRAGMA journal_mode = WAL; -- Performance: abilita il Write-Ahead Logging
PRAGMA foreign_keys = ON; -- Integrità: attiva i vincoli di chiave esterna
PRAGMA cache_size = -64000; -- Performance: 64 MiB di cache in RAM
PRAGMA synchronous = NORMAL; -- Performance: bilanciamento sicuro con WAL
PRAGMA busy_timeout = 5000; -- Concorrenza: attende 5s se il DB è bloccato
"""
# Schema del database
SCHEMA_SCRIPT = """
CREATE TABLE IF NOT EXISTS autori (
id INTEGER PRIMARY KEY,
nome TEXT NOT NULL
);
CREATE TABLE IF NOT EXISTS libri (
id INTEGER PRIMARY KEY,
titolo TEXT NOT NULL,
autore_id INTEGER,
FOREIGN KEY (autore_id) REFERENCES autori (id)
);
"""
def pulisci_db():
"""Funzione helper per rimuovere il DB precedente."""
if os.path.exists(DB_FILE):
os.remove(DB_FILE)
print(f"\n--- Database '{DB_FILE}' precedente rimosso. ---")
# --- TEST 1: Connessione di default (SENZA PRAGMA) ---
pulisci_db()
print("▶️ TEST 1: Connessione Python standard (PRAGMA di default)")
try:
# Connessione standard
conn = sqlite3.connect(DB_FILE)
conn.executescript(SCHEMA_SCRIPT) # Crea le tabelle
# Inserisco un autore valido
conn.execute("INSERT INTO autori (id, nome) VALUES (1, 'Alessandro Manzoni')")
# Tento di inserire un libro con autore_id=99 (che NON ESISTE)
print("Tento di inserire un libro con autore_id=99 (inesistente)...")
conn.execute("INSERT INTO libri (titolo, autore_id) VALUES ('Libro Orfano', 99)")
conn.commit()
# Verifichiamo cosa c'è nel database
cursor = conn.execute("SELECT * FROM libri WHERE autore_id = 99")
risultato = cursor.fetchone()
print(f"❌ PROBLEMA: L'inserimento è riuscito!")
print(f"Dati corrotti nel DB: {risultato}")
except Exception as e:
print(f"Errore inatteso: {e}")
finally:
if 'conn' in locals():
conn.close()
# --- TEST 2: Connessione ottimizzata (CON PRAGMA) ---
pulisci_db()
print("▶️ TEST 2: Connessione Python ottimizzata (CON PRAGMA)")
try:
# Connessione
conn = sqlite3.connect(DB_FILE)
# --- LA DIFFERENZA CHIAVE È QUI ---
# Applichiamo i nostri PRAGMA prima di fare qualsiasi altra cosa
print("Applico i PRAGMA (WAL, Foreign Keys, Cache...).")
conn.executescript(PRAGMA_SCRIPT)
# ------------------------------------
conn.executescript(SCHEMA_SCRIPT) # Crea le tabelle
# Inserisco un autore valido
conn.execute("INSERT INTO autori (id, nome) VALUES (1, 'Alessandro Manzoni')")
# Tento di inserire un libro con autore_id=99 (che NON ESISTE)
print("Tento di inserire un libro con autore_id=99 (inesistente)...")
conn.execute("INSERT INTO libri (titolo, autore_id) VALUES ('Libro Orfano', 99)")
conn.commit() # Questa riga non dovrebbe mai essere raggiunta
print("❌ ERRORE: L'inserimento non doveva riuscire!")
except sqlite3.IntegrityError as e:
# Questo è l'errore che CI ASPETTIAMO!
print(f"✅ SUCCESSO: Il database ha bloccato l'inserimento.")
print(f"Errore ricevuto (correttamente): {e}")
except Exception as e:
print(f"Errore inatteso: {e}")
finally:
if 'conn' in locals():
conn.close()
▶️ TEST 1: Connessione Python standard (PRAGMA di default) Tento di inserire un libro con autore_id=99 (inesistente)... ❌ PROBLEMA: L'inserimento è riuscito! Dati corrotti nel DB: (1, 'Libro Orfano', 99) --- Database 'database_demo.db' precedente rimosso. --- ▶️ TEST 2: Connessione Python ottimizzata (CON PRAGMA) Applico i PRAGMA (WAL, Foreign Keys, Cache...). Tento di inserire un libro con autore_id=99 (inesistente)... ✅ SUCCESSO: Il database ha bloccato l'inserimento. Errore ricevuto (correttamente): FOREIGN KEY constraint failed
Cosa È Successo?
conn.executescript(PRAGMA_SCRIPT): Questo è il comando fondamentale.executescriptpermette di eseguire una stringa contenente più istruzioni SQL (separate da punto e virgola), rendendolo perfetto per impostare tutti i PRAGMA in un colpo solo.- Test 1 (Fallito): Senza
PRAGMA foreign_keys = ON;, Python e SQLite eseguono felicemente l’istruzioneINSERTanche se viola un vincolo. Questo porta a dati corrotti (un libro che fa riferimento a un autore inesistente). - Test 2 (Riuscito): Attivando i PRAGMA all’apertura della connessione, il motore SQLite si è “svegliato”. Quando abbiamo tentato di eseguire lo stesso
INSERT, ha controllato il vincolo, ha visto cheautore_id=99non esisteva e ha sollevato un’eccezionesqlite3.IntegrityError: FOREIGN KEY constraint failed. Questo è il comportamento corretto che protegge i tuoi dati.
Mentre foreign_keys è l’unico di cui vedi l’effetto immediato, gli altri PRAGMA (WAL, cache_size, ecc.) stanno lavorando in background per rendere il tuo database molto più veloce e robusto sotto carico.
Conclusione
I PRAGMA non sono “optional”.
Sono il pulsante turbo che separa un database lento e fragile da un motore solido, usato nei sistemi reali che servono miliardi di utenti.
Chi li ignora, perde fino al 90% delle performance, rischia database corrotti e passa notti a fare debugging su dati incoerenti.
Chi li usa, gestisce milioni di record su app da 5 MB, fa deploy su miliardi di dispositivi (come WhatsApp o Android), e soprattutto… dorme sonni tranquilli. 😴
Approfondisci su La Scienza dei Dati
- 💾 Guida pratica a SQLite3 in Python: da zero a Pandas con esempi e best practice
- 💡 6 esercizi pratici con Pandas e SQLite per l’analisi dati
- 🧩 Importare CSV in SQLite con Python: guida e soluzioni agli 8 errori più comuni
- 🗃️ Progettare database relazionali: dall’ERD all’integrità dei dati
- 🧮 Normalizzazione database: guida pratica con esempio reale





