Sviluppo web
Transazioni ACID in pratica
Una transazione ACID è un confine: entrano tutte le scritture o nessuna. Il commit costa 0,330 ms con synchronous FULL e 0,018 ms con NORMAL.
In sintesi
- Su 200 transazioni da una riga, il commit costa 0,330 ms in SQLite con synchronous FULL e 0,018 ms con NORMAL.
- Un vincolo CHECK violato a metà transazione lascia entrambi i saldi a 100: il rollback annulla la prima scrittura.
- NORMAL e OFF nell'SQLite in modalità WAL riducono il costo del commit e indeboliscono la durabilità, non l'atomicità.
- Una transazione non deve contenere chiamate di rete: il lock resta aperto per tutta la sua durata.
Una transazione ACID è un confine: o tutte le scritture entrano, o nessuna. Misurato su 200 transazioni da una riga in SQLite 3.51.3, il commit costa 0,330 ms con synchronous FULL e 0,018 ms con NORMAL: la differenza è una chiamata di sincronizzazione al disco.
Cosa promette ACID, nell'ordine in cui si rompe
Atomicità significa che il gruppo di scritture è indivisibile. Coerenza significa che i vincoli valgono alla fine: un saldo non negativo, una chiave esterna esistente. Isolamento significa che un'altra connessione non vede uno stato intermedio. Durabilità significa che un commit sopravvive a un crash. Le prime tre si perdono per errori nel codice, la quarta si perde per una configurazione del disco.
Il rollback, misurato
Con due conti da 100, un addebito da 60 sul primo e uno da 150 sul secondo, il secondo UPDATE viola il CHECK e la transazione va in errore. Dopo ROLLBACK entrambi i saldi sono ancora 100. La prima scrittura non è rimasta: questo è il comportamento che rende utile una transazione, e non dipende dalla velocità del disco.
Il prezzo del commit
Con synchronous FULL il commit scrive il WAL e chiede al disco di confermare: 0,330 ms per transazione su questa macchina. Con NORMAL la conferma arriva al checkpoint e il costo scende a 0,018 ms. La documentazione SQLite, letta il 1 novembre 2026, avverte che in modalità WAL NORMAL può perdere gli ultimi commit dopo un'interruzione di corrente, mentre OFF è sconsigliato anche in caso di crash dell'applicazione.
node -e "const {DatabaseSync}=require('node:sqlite');const db=new DatabaseSync('app.db');db.exec('PRAGMA journal_mode=WAL');db.exec('BEGIN IMMEDIATE');try{db.exec('UPDATE accounts SET balance=balance-60 WHERE id=1');db.exec('UPDATE accounts SET balance=balance-150 WHERE id=2');db.exec('COMMIT')}catch(e){db.exec('ROLLBACK');console.log('rolled back:',e.message)}"
Transazioni brevi, non transazioni lunghe
Una transazione deve contenere solo il lavoro che dipende dal database. Se dentro c'è una chiamata HTTP, un caricamento di file o un'attesa su una coda, il lock resta aperto per tutta l'attesa e le altre scritture si accodano. La regola pratica è: leggi, decidi, scrivi, chiudi. Se una decisione richiede una risposta esterna, prendila prima e riprova se lo stato è cambiato, invece di tenere aperta la transazione.
Dove finisce il confine
In PostgreSQL il livello predefinito è READ COMMITTED: ogni istruzione vede i commit avvenuti prima del suo inizio, quindi due letture nella stessa transazione possono restituire valori diversi. SQLite, con un solo scrittore alla volta, offre un isolamento più semplice. Se il codice assume letture ripetibili, il livello va dichiarato: è una scelta del programmatore, non un dettaglio del database.
Il limite: durabilità non è atomicità
Abbassare synchronous rende il commit veloce senza rompere l'atomicità: il rollback funziona lo stesso, ma un crash può cancellare le ultime transazioni già confermate. Per un registro contabile questo è inaccettabile, per una cache di preferenze è irrilevante. E nessuna configurazione protegge da un disco che dichiara di aver scritto quando non l'ha fatto: la durabilità finisce dove finisce l'hardware.
Da dove partire
Domande frequenti
Che differenza c'è fra transazione e batch?
Il batch è un modo di raggruppare le scritture per velocità, la transazione definisce cosa è atomico. Un batch dentro una transazione può essere grande, ma resta un unico commit: se una riga viola un vincolo, torna indietro tutto.
Perché il commit è più lento con synchronous FULL?
Perché aspetta la conferma del disco. Su questa macchina costa 0,330 ms contro 0,018 ms con NORMAL, e la differenza è il fsync. Il prezzo si paga una volta per transazione, non una volta per riga.
Posso usare synchronous OFF in produzione?
No per i dati che non puoi ricostruire. OFF riduce il costo del commit ma la documentazione SQLite lo sconsiglia anche in caso di crash dell'applicazione, non solo di corrente: per soldi e prenotazioni resta FULL.
Una transazione lunga cosa blocca?
Blocca le altre scritture sulle righe toccate e impedisce il recupero dello spazio. Con una chiamata di rete al suo interno il lock dura quanto la rete, quindi la transazione va chiusa prima di ogni attesa esterna.
Serve una transazione per una sola riga?
Se l'operazione è una sola istruzione, il database la esegue già in modo atomico. La transazione serve quando più istruzioni devono entrare insieme, per esempio un addebito e un accredito.