SERIALIZABLE è il livello massimo di isolamento delle transazioni in PostgreSQL. Questo livello garantisce che i risultati delle transazioni parallele saranno gli stessi come se fossero eseguite IN SEQUENZA, una dopo l’altra. Nessuna anomalia di esecuzione parallela (tipo Dirty Read, Non-Repeatable Read, Phantom Read) può succedere.
In parole povere, SERIALIZABLE mette ordine totale e coerenza tra le transazioni parallele. È come se PostgreSQL dicesse: "Tutti in fila, ragazzi!"
Perché serve il livello SERIALIZABLE? A volte vuoi essere sicuro al 100% che i tuoi dati restino sempre coerenti, anche se ci sono modifiche parallele. Immagina una scena al supermercato, dove i cassieri servono i clienti contemporaneamente. Se nessuno controllasse la fila, alla fine ci sarebbero più prodotti fuori dal negozio di quanti ne sono stati acquistati. Con SERIALIZABLE una cosa del genere è semplicemente impossibile.
Esempio di impostazione del livello SERIALIZABLE
Per impostare il livello di isolamento SERIALIZABLE, usa il comando:
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
Per esempio, creiamo una transazione che usa questo livello:
BEGIN; -- Inizio transazione
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE; -- Impostiamo il livello di isolamento
SELECT * FROM products WHERE category = 'Electronics'; -- Prendiamo la lista dei prodotti
UPDATE products SET stock = stock - 1 WHERE product_id = 123; -- Aggiorniamo lo stock
COMMIT; -- Confermiamo le modifiche
Case: prenotazione biglietti al cinema
Dai, vediamo un esempio reale dove il livello SERIALIZABLE è proprio fondamentale. Immagina che stai sviluppando un sistema di prenotazione online dei biglietti per il cinema. Gli utenti scelgono i posti e tu vuoi garantire che lo stesso posto non venga venduto a due clienti contemporaneamente.
Prima creiamo una tabella per i posti:
CREATE TABLE seats (
seat_id SERIAL PRIMARY KEY,
is_booked BOOLEAN DEFAULT FALSE
);
Ora aggiungiamo qualche posto:
INSERT INTO seats (is_booked) VALUES (FALSE), (FALSE), (FALSE);
Ecco un esempio di transazione con SERIALIZABLE.
Così puoi fare una prenotazione sicura di un posto:
BEGIN; -- Inizio transazione
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE; -- Livello di isolamento SERIALIZABLE
-- Controlliamo che il posto sia libero
SELECT is_booked FROM seats WHERE seat_id = 1;
-- Prenotiamo il posto
UPDATE seats SET is_booked = TRUE WHERE seat_id = 1;
COMMIT; -- Confermiamo la prenotazione
Se una seconda transazione parallela prova a prenotare lo stesso posto, PostgreSQL non farà confusione e lancerà un errore di conflitto di serializzazione.
Prevenzione di Phantom Read
Ora vediamo le "letture fantasma", da cui volevamo liberarci. Phantom Read succede quando una transazione vede cambiamenti nei dati aggiunti da un’altra transazione mentre è ancora in corso. Per esempio, la tua transazione si aspetta un certo numero di righe, ma all’improvviso un’altra transazione aggiunge o cancella righe, cambiando i risultati.
Guarda l’esempio:
Dati prima dell’inizio delle transazioni
| id | balance | user |
|---|---|---|
| 1 | 1000 | Alice |
| 2 | 500 | Bob |
Transazione 1
BEGIN;
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
-- Conta gli utenti con saldo maggiore di 400
SELECT COUNT(*) FROM accounts WHERE balance > 400;
-- Ci aspettiamo: 2 (Alice e Bob)
Transazione 2
In un’altra sessione parte una transazione parallela:
BEGIN;
INSERT INTO accounts (id, balance, user) VALUES (3, 700, 'Charlie');
COMMIT;
Torniamo alla Transazione 1
-- Ripetiamo la query
SELECT COUNT(*) FROM accounts WHERE balance > 400;
Ora, se non usi SERIALIZABLE, il risultato sarà 3 invece di 2, perché Charlie è stato aggiunto mentre la Transazione 1 era ancora attiva. Questo è proprio il Phantom Read.
Ma con SERIALIZABLE PostgreSQL garantisce che la Transazione 1 non vedrà Charlie, perché la sua "visione del mondo" è congelata al momento dell’inizio della transazione.
Caratteristiche e limiti del livello SERIALIZABLE
Abbiamo visto come SERIALIZABLE ti aiuta ad avere isolamento perfetto. Ma cosa c’è di perfetto senza qualche difetto? Parliamoci chiaro.
Prestazioni più basse
SERIALIZABLE richiede molte più risorse rispetto ai livelli READ COMMITTED o REPEATABLE READ. Perché? PostgreSQL deve simulare l’esecuzione in sequenza delle operazioni, controllando tutti i possibili conflitti tra le transazioni.
Errori di serializzazione
Se PostgreSQL vede che non può eseguire le transazioni in "sequenza perfetta", genera un errore di serializzazione (serialization_failure) e fa il rollback della transazione.
Esempio di errore:
ERROR: could not serialize access due to concurrent update
Per gestire queste situazioni, puoi rilanciare la transazione dopo il fallimento:
DO $$
DECLARE
done BOOLEAN := FALSE;
BEGIN
WHILE NOT done LOOP
BEGIN
-- Inizio transazione
BEGIN;
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
-- Facciamo le operazioni
UPDATE accounts SET balance = balance - 100 WHERE id = 1;
-- Confermiamo le modifiche
COMMIT;
done := TRUE; -- Esci dal ciclo se tutto ok
EXCEPTION WHEN serialization_failure THEN
ROLLBACK; -- Rollback in caso di errore
END;
END LOOP;
END;
$$;
Questo è il modo classico nei sistemi dove si usa SERIALIZABLE.
Questo codice è scritto usando PL-SQL. Ci torneremo più avanti. Volevo solo darti un esempio bello e funzionante. E anche farti vedere a cosa serve PL-SQL :)
Quando usare SERIALIZABLE?
Questo livello di isolamento ha senso dove l’errore costa caro:
- Transazioni finanziarie, tipo gestione pagamenti o distribuzione di bonus.
- Sistemi di gestione delle scorte, per evitare ordini doppi.
- Prenotazioni online, dove è fondamentale evitare conflitti sulle risorse.
Se stai sviluppando un sistema dove i dati devono essere coerenti al 100% e le prestazioni non sono la priorità, SERIALIZABLE sarà il tuo migliore amico.
GO TO FULL VERSION