SQLite è un database relazionale embedded che funziona senza un processo server separato e memorizza l'intero database in un singolo file sul dispositivo. Grazie alla configurazione zero, alle dimensioni ridotte della libreria e al supporto completo di SQL, SQLite è diventato lo standard per l'archiviazione locale dei dati nelle applicazioni mobili. Secondo il SQLite Consortium (2025), questo DBMS è utilizzato su oltre 4 miliardi di dispositivi, inclusi tutti gli smartphone con iOS e Android.
Punti chiave
SQLite è una libreria in linguaggio C che implementa un DBMS relazionale senza server dedicato. È incorporata direttamente nell'applicazione, leggendo e scrivendo dati in un normale file sul filesystem del dispositivo. La dimensione della libreria è di circa 600 KB, rendendo SQLite il database SQL completo più leggero.
SQLite supporta la maggior parte dello standard SQL:1999, inclusi JOIN, sottoquery, trigger, viste, indici e funzioni finestra. Le limitazioni riguardano ALTER TABLE (supporto limitato) e i RIGHT/FULL OUTER JOIN completi. Tuttavia, per le applicazioni mobili, la funzionalità di SQLite è sufficiente nel 99% dei casi di archiviazione locale.
Secondo il sondaggio sviluppatori di Stack Overflow (2025), SQLite è il database più popolare per soluzioni embedded e occupa il terzo posto in popolarità tra tutti i DBMS dopo MySQL e PostgreSQL. Nello sviluppo mobile, SQLite è utilizzato in ogni applicazione — direttamente o tramite wrapper.
Configurazione zero — SQLite non richiede installazione, impostazione dei permessi, creazione di utenti o avvio del servizio. La libreria viene collegata al progetto e il database viene creato chiamando una singola funzione. Ciò semplifica radicalmente il deployment rispetto ai DBMS client-server, che richiedono installazione del server, configurazione delle porte e creazione degli utenti.
Il file del database SQLite è un normale file multipiattaforma che può essere copiato, analizzato, inviato in rete o ripristinato da un backup. Il formato del file è stabile a livello API: i file SQLite 3 creati nel 2004 si aprono con la versione corrente della libreria, garantendo la compatibilità dei dati a lungo termine.
L'architettura di SQLite è composta da otto macchine virtuali: Tokenizer, Parser, Code Generator, VM, B-Tree, Pager, OS Interface e Utilities. Una query SQL passa attraverso Tokenizer (suddivisione in token), Parser (costruzione di un AST), Code Generator (conversione in bytecode) e viene eseguita sulla macchina virtuale, che legge le pagine dei dati tramite B-Tree e Pager.
SQLite utilizza B-Tree per memorizzare tabelle e indici. Ogni tabella è memorizzata come un B-Tree separato, dove i nodi foglia contengono le righe di dati. Anche gli indici sono memorizzati come B-Tree, ma con chiavi nelle foglie. Il Pager gestisce il caricamento delle pagine (default 4096 byte) dal file alla memoria, fornendo transazioni ACID tramite journal o WAL.
WAL (Write-Ahead Logging) è la modalità consigliata per le applicazioni mobili. Le modifiche vengono prima scritte in un file WAL separato e poi trasferite periodicamente al database principale. Il WAL consente di leggere (dati vecchi) e scrivere (tramite WAL) simultaneamente nel database, migliorando le prestazioni delle applicazioni multithread. Il journal standard (rollback journal) blocca la lettura durante la scrittura.
| Parametro | Rollback Journal | WAL (Write-Ahead Logging) |
|---|---|---|
| Lettura durante scrittura | Bloccata | Consentita (legge dati vecchi) |
| Prestazioni di scrittura | Medie | Elevate (scrittura sequenziale in WAL) |
| Consumo disco | Inferiore (solo journal di rollback) | Maggiore (WAL + database principale) |
| Recupero da crash | Rollback all'ultimo checkpoint | Recupero da WAL (nessuna perdita dati) |
| Raccomandazione | Per scenari single-thread | Per applicazioni mobili tipiche |
Il passaggio tra le modalità viene effettuato con una singola query SQL: PRAGMA journal_mode=WAL. Per le applicazioni mobili con sincronizzazione in background e un thread UI che legge dati simultaneamente, il WAL offre prestazioni migliori e assenza di blocchi dell'interfaccia.
SQLite non è l'unica opzione per l'archiviazione locale dei dati, ma è la più versatile. Realm offre una maggiore velocità di accesso diretto agli oggetti in memoria, ma utilizza un proprio formato NoSQL e ha una dimensione della libreria maggiore. Core Data su iOS è un livello ORM sopra SQLite che aggiunge la gestione del grafo degli oggetti e l'annullamento delle operazioni.
Per la maggior parte delle applicazioni, SQLite rimane la scelta ottimale grazie alle prestazioni prevedibili, all'assenza di vendor lock-in e alla stabilità collaudata nel tempo. Realm e Core Data sono giustificati in progetti con grafi di oggetti complessi, query reattive o requisiti di sincronizzazione tra dispositivi.
| Caratteristica | SQLite | Realm | Core Data |
|---|---|---|---|
| Tipo di database | Relazionale (SQL) | NoSQL (orientato agli oggetti) | ORM (su SQLite) |
| Dimensione libreria | ~600 KB | ~4 MB | Integrato nell'SDK Apple |
| Prestazioni | Medie | Elevate (oggetti in memoria) | Medie (overhead ORM) |
| Piattaforme | iOS, Android, Web, Desktop | iOS, Android, Node.js | iOS, macOS |
| Vendor lock-in | Nessuno (standard aperto) | Medio (formato proprietario) | Elevato (solo Apple) |
La scelta tra SQLite, Realm e Core Data dipende dalla piattaforma, dai requisiti del modello oggetti e dalla strategia di sincronizzazione. Per i progetti multipiattaforma (KMP, Flutter), SQLite rimane l'unica scelta universale che funziona su tutte le piattaforme target senza modifiche al modello dati.
Room è una libreria di Android Jetpack che fornisce un livello ORM sopra SQLite. Room genera automaticamente query SQL da interfacce DAO annotate, verifica la correttezza delle query in fase di compilazione e supporta le migrazioni del database quando lo schema cambia. Room è il modo consigliato per lavorare con SQLite su Android.
SQLiteOpenHelper è un'API di basso livello per la gestione diretta di SQLite senza ORM. La classe gestisce la creazione, l'apertura e l'aggiornamento del database. SQLiteOpenHelper è adatto per progetti con query SQL semplici o quando è necessario il controllo completo sulla logica SQL senza l'astrazione di Room.
Un'entità in Room è annotata con @Entity e un DAO con @Dao. Room traduce i metodi annotati in query SQL: @Insert genera INSERT, @Query genera SELECT con il SQL specificato. Le migrazioni vengono aggiunte tramite Migration con le versioni dello schema vecchia e nuova specificate. Room valida il SQL in fase di compilazione, eliminando gli errori di sintassi in produzione.
@Entity
data class User(
@PrimaryKey val id: Long,
val name: String,
@ColumnInfo(name = "created_at")
val createdAt: Long
)
@Dao
interface UserDao {
@Query("SELECT * FROM user ORDER BY name ASC")
suspend fun getAllUsers(): List<User>
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertUser(user: User)
@Query("DELETE FROM user WHERE id = :id")
suspend fun deleteUser(id: Long)
}
Room genera automaticamente l'implementazione UserDao_Impl, che contiene query SQLite in esecuzione tramite il RoomDatabase interno. Grazie alle coroutine (suspend), i metodi DAO vengono eseguiti in modo asincrono su un thread in background senza bloccare l'UI. I tipi di ritorno Flow in @Query aggiornano automaticamente il risultato quando la tabella cambia.
FMDB è un wrapper Objective-C sull'API C di SQLite, storicamente la prima libreria popolare per iOS. Fornisce oggetti FMDatabase e FMResultSet per eseguire query e ottenere risultati. FMDB è semplice e minimalista, ma non supporta costrutti specifici di Swift — optionals, Codable, async/await.
GRDB è una libreria Swift moderna per lavorare con SQLite. Fornisce un'API type-safe, supporto per Codable, Combine Publishers, async/await, migrazioni e osservazione dei cambiamenti in tempo reale. GRDB è preferita per i nuovi progetti Swift grazie alla piena integrazione con Swift Concurrency e a una migliore leggibilità del codice.
GRDB definisce le tabelle tramite classi Record che conformano ai protocolli FetchableRecord e TableRecord. Le query sono scritte in Swift con sintassi type-safe invece di SQL puro. GRDB supporta anche DatabaseMigrator per il versionamento dello schema e le migrazioni tra le versioni dell'app.
struct User: Codable, FetchableRecord, TableRecord {
var id: Int64
var name: String
var createdAt: Date
}
let dbPool = try DatabasePool(path: dbPath)
var migrator = DatabaseMigrator()
migrator.registerMigration("v1") { db in
try db.create(table: "user") { t in
t.autoIncrementedPrimaryKey("id")
t.column("name", .text).notNull()
t.column("createdAt", .datetime).notNull()
}
}
let users = try await dbPool.read { db in
try User.order(Column("name")).fetchAll(db)
}
DatabasePool utilizza la modalità WAL di SQLite per la lettura concorrente. Più lettori possono accedere contemporaneamente al database mentre un singolo scrittore aggiorna i dati tramite WAL. GRDB gestisce automaticamente connessioni e transazioni, fornendo accesso thread-safe al database da qualsiasi thread senza sincronizzazione manuale.
Gli indici sono il modo più efficace per accelerare le query SQLite. Un indice viene creato sulle colonne utilizzate nelle clausole WHERE, JOIN e ORDER BY. Per una tabella con 100000 record, la ricerca su una colonna indicizzata richiede millisecondi invece di secondi. Tuttavia, gli indici rallentano le operazioni INSERT e UPDATE, quindi il loro numero deve essere bilanciato con la frequenza di scrittura.
L'inserimento batch (batch insert) all'interno di una singola transazione accelera radicalmente il caricamento massivo di dati. Inserire 1000 record uno per uno genera un overhead di circa 1 secondo. Gli stessi 1000 record in una singola transazione richiedono circa 5-10 millisecondi. La differenza è spiegata dal fatto che ogni INSERT individuale crea una nuova transazione con scrittura sincrona su disco.
PRAGMA sono comandi SQLite per configurare il comportamento della libreria. Pragmas di ottimizzazione chiave: PRAGMA synchronous=NORMAL (riduce la frequenza di fsync), PRAGMA cache_size=-8000 (alloca 8 MB di cache), PRAGMA temp_store=MEMORY (tabelle temporanee in memoria). Per le applicazioni mobili con grandi volumi di dati, la combinazione di questi pragmas accelera le query di 2-3 volte.
Un'altra ottimizzazione importante è la precompilazione delle query SQL (prepared statements). Se una query viene eseguita ripetutamente (ad esempio, inserendo 10000 righe), compilare SQL una volta e riutilizzare l'istruzione riduce il carico della CPU del 30-50%. Room e GRDB memorizzano automaticamente nella cache i prepared statements, ma quando si utilizza direttamente l'API C di SQLite, la compilazione deve essere eseguita manualmente.
class UserRepository(private val db: RoomDatabase) {
suspend fun insertBatch(users: List<User>) {
db.withTransaction {
users.chunked(500).forEach { batch ->
batch.forEach { user ->
insertUser(user)
}
}
}
}
}
L'inserimento batch con withTransaction garantisce che tutte le operazioni INSERT vengano eseguite all'interno di una singola transazione. La suddivisione in sottobatch (chunked) impedisce a una singola transazione di diventare troppo grande, il che potrebbe bloccare altri thread per un periodo prolungato. Per la sincronizzazione in background, una dimensione del sottobatch di 500 record fornisce l'equilibrio ottimale tra velocità e reattività dell'UI.
Domande frequenti
Sì, SQLite supporta l'accesso multi-thread in modalità WAL. Più thread possono leggere dati contemporaneamente, ma solo uno può scrivere. Room e GRDB gestiscono la sincronizzazione automaticamente. In modalità rollback journal (predefinita), il database viene completamente bloccato durante qualsiasi operazione di scrittura.
Il limite di SQLite è di 281 TB (massimo teorico). In pratica, la dimensione del database è limitata dalla memoria disponibile del dispositivo. Per le applicazioni mobili, una dimensione confortevole è fino a 1-2 GB. I database più grandi di 2 GB rallentano i backup, gli aggiornamenti tramite App Store e aumentano il consumo di RAM.
SQLite non crittografa i dati per impostazione predefinita — qualsiasi processo con accesso al file può leggerli. Per la crittografia, utilizzare SQLCipher (estensione con AES-256), Room con EncryptedDatabase (Android) o Encrypted Core Data su iOS. La crittografia aggiunge un overhead del 5-15% alle operazioni di lettura e scrittura dei dati.
SQLite è una libreria embedded che non richiede un processo server. MySQL è un DBMS client-server con un server separato, utenti, diritti di accesso e un protocollo di rete. SQLite memorizza il database in un singolo file; MySQL lo memorizza in più file gestiti dal server. SQLite è più semplice e leggero; MySQL è più potente e scalabile.
Per la migrazione di SQLite, utilizzare ALTER TABLE (aggiunta di colonne) o creare una nuova tabella con trasferimento dei dati ed eliminazione della vecchia tabella. Room automatizza questo processo tramite le classi Migration: specificare startVersion, endVersion e le query SQL per le modifiche dello schema. GRDB e FMDB forniscono DatabaseMigrator simili.
Riepilogo
Svilupperemo un'applicazione mobile chiavi in mano
IT Sectr crea applicazioni iOS e Android per startup e aziende dal 2017. Ti consulteremo e ti proporremo la soluzione migliore.
Leggi anche