Facciamo ancora un giro sui tipi di transazioni! Nel mondo delle transazioni nei database ci sono tre principali "cattivi" che possono rovinarti la giornata: Dirty Read, Non-Repeatable Read e Phantom Read. Queste "anomalie" spuntano fuori quando il livello di isolamento delle transazioni non è abbastanza alto. Oggi vediamo chi sono questi cattivi, come si manifestano e — cosa più importante — come combatterli.
Prima di passare agli esempi, ricordiamo cosa significano.
Dirty Read (Lettura sporca):
Stai leggendo dati che sono stati modificati, ma la transazione che li ha cambiati non è ancora stata confermata (COMMIT) o, peggio, potrebbe essere annullata (ROLLBACK). È come se avessi inviato soldi a un amico, guardato il tuo saldo e visto che sei al verde, ma poi ci hai ripensato e ti sei ripreso i soldi. Magia!Non-Repeatable Read (Lettura non ripetibile):
Leggi gli stessi dati due volte all'interno della stessa transazione, ma tra le due letture questi dati vengono modificati da un'altra transazione, e vedi due risultati diversi. È come se guardassi la data di nascita sul passaporto, poi lo dai a un amico che cambia i numeri, e quando lo riguardi la tua data è cambiata.Phantom Read (Lettura fantasma):
Fai la stessa query due volte, ma la seconda volta vedi delle righe in più aggiunte da un'altra transazione. È come se stessi contando le persone in una stanza, e qualcuno di nascosto porta dentro altri amici.
Problema Dirty Read
Supponiamo di avere una tabella accounts:
CREATE TABLE accounts (
account_id SERIAL PRIMARY KEY,
owner TEXT NOT NULL,
balance NUMERIC(10, 2) NOT NULL
);
INSERT INTO accounts (owner, balance) VALUES ('Alice', 1000), ('Bob', 500);
La Transazione 1 modifica il saldo, ma non è ancora finita, mentre la Transazione 2 cerca di leggere questi dati nello stesso momento.
Transazione 1:
BEGIN;
UPDATE accounts SET balance = balance - 200 WHERE owner = 'Alice';
-- Il saldo di Alice è diventato 800, ma la transazione non è ancora conclusa.
Transazione 2:
BEGIN;
SELECT balance FROM accounts WHERE owner = 'Alice'; -- Vediamo saldo: 800 (lettura sporca).
ROLLBACK; -- La Transazione 1 viene annullata.
Ora la Transazione 2 lavora con dati non corretti, perché la Transazione 1 ha annullato le modifiche. Questo si può evitare usando il livello di isolamento READ COMMITTED, che non permette di vedere i cambiamenti delle transazioni non confermate.
Problema Non-Repeatable Read
Immagina che la Transazione 1 legga dei dati, un'altra transazione li modifica, e la Transazione 1 li legge di nuovo. I dati sono diversi.
Transazione 1:
BEGIN;
SELECT balance FROM accounts WHERE owner = 'Bob'; -- Vediamo saldo: 500.
Transazione 2 in parallelo:
BEGIN;
UPDATE accounts SET balance = balance - 100 WHERE owner = 'Bob';
COMMIT;
Transazione 1 di nuovo:
SELECT balance FROM accounts WHERE owner = 'Bob'; -- Vediamo saldo: 400.
COMMIT;
Nota come i dati all'interno della stessa transazione sono cambiati. In scenari reali può essere critico, ad esempio per i report finanziari. Il problema si risolve usando un livello di isolamento più alto, tipo REPEATABLE READ.
Problema Phantom Read
Supponiamo di avere una tabella orders:
CREATE TABLE orders (
order_id SERIAL PRIMARY KEY,
customer TEXT NOT NULL,
total NUMERIC(10, 2) NOT NULL
);
INSERT INTO orders (customer, total) VALUES ('Alice', 100), ('Bob', 200);
La Transazione 1 conta il numero di ordini, un'altra transazione aggiunge un nuovo ordine, e la Transazione 1 conta di nuovo.
Transazione 1:
BEGIN;
SELECT COUNT(*) FROM orders; -- Vediamo: 2.
Transazione 2 in parallelo:
BEGIN;
INSERT INTO orders (customer, total) VALUES ('Charlie', 300);
COMMIT;
Transazione 1 di nuovo:
SELECT COUNT(*) FROM orders; -- Vediamo: 3. È apparso un nuovo ordine "fantasma"!
COMMIT;
Per eliminare le letture fantasma serve il livello di isolamento SERIALIZABLE, che blocca completamente le modifiche parallele che influenzano il risultato.
Modi per prevenire le anomalie
Livelli di isolamento vs. Anomalie
| Livello di isolamento | Dirty Read |
Non-Repeatable Read |
Phantom Read |
|---|---|---|---|
Read Uncommitted |
❌ Sì | ❌ Sì | ❌ Sì |
Read Committed |
✅ No | ❌ Sì | ❌ Sì |
Repeatable Read |
✅ No | ✅ No | ❌ Sì |
Serializable |
✅ No | ✅ No | ✅ No |
Come scegliere il livello di isolamento giusto?
- Se ti serve accesso veloce ai dati e le anomalie non sono critiche (tipo analisi su dati vecchi) — usa
READ COMMITTED. - Se vuoi dati stabili all'interno della stessa transazione — usa
REPEATABLE READ. - Se vuoi la massima isolamento e coerenza — scegli
SERIALIZABLE. Ricorda: la performance potrebbe risentirne.
Consigli pratici
Usa transazioni con livelli di isolamento adatti al tuo caso. Per esempio:
SET TRANSACTION ISOLATION LEVEL REPEATABLE READ;
BEGIN;
SELECT ...;
COMMIT;
Aggiungi indici per minimizzare i lock sulle tabelle e velocizzare le query.
Usa query ottimizzate per evitare lock lunghi, soprattutto a livello SERIALIZABLE.
Errori specifici e come evitarli
A volte un livello di isolamento scelto male causa conflitti e cali di performance. Ad esempio, usare SERIALIZABLE in un sistema con tante transazioni parallele può portare a lock e "fame" di transazioni.
Per evitarlo, analizza le tue query, testa la performance con diversi livelli di isolamento e usa gli indici giusti.
GO TO FULL VERSION