Le transazioni in PostgreSQL garantiscono l'isolamento — una delle caratteristiche chiave di ACID. Per farlo, il sistema usa i lock. Un lock è un meccanismo che assicura che più transazioni non "combattano" per la stessa riga o tabella. Immagina una fila al supermercato: alla cassa viene servita una persona alla volta. I lock funzionano più o meno così, solo che gestiscono l'accesso ai dati nelle tabelle.
Tipi principali di lock
In PostgreSQL ci sono diversi tipi di lock, alcuni dei quali forse hai già visto in EXPLAIN ANALYZE:
ROW EXCLUSIVE(lock esclusivo sulla riga) — succede quando modifichi i dati in una riga. È il tipo di lock più usato, ad esempio quando faiINSERT,UPDATEoDELETE.SHARE(lock condiviso) — si usa per operazioni che garantiscono che i dati non cambino mentre la query è in esecuzione, tipo quando faiSELECT ... FOR SHARE.EXCLUSIVE(lock esclusivo) — impedisce qualsiasi altra operazione sui dati, tranne la lettura.
Si può dire che i lock sono come dei cani da guardia che proteggono i nostri dati.
Problema dei lock: cosa può andare storto?
Ora che abbiamo capito cosa sono i lock, vediamo quali problemi possono causare. Il più grande è il problema dei deadlock. Un deadlock è una situazione in cui due (o più) transazioni si aspettano a vicenda, e così l'esecuzione rimane bloccata in un ciclo infinito. Di solito succede così:
- La transazione 1 blocca la riga A e vuole accedere alla riga B.
- La transazione 2 blocca la riga B e vuole accedere alla riga A.
- Dato che entrambe le righe sono bloccate, nessuna delle due transazioni può finire.
Esempio di deadlock
Per sentire il dolore di uno sviluppatore che si becca un deadlock, immagina questo scenario:
-- Transazione 1
BEGIN;
UPDATE accounts SET balance = balance - 100 WHERE id = 1;
-- Allo stesso tempo
-- Transazione 2
BEGIN;
UPDATE accounts SET balance = balance + 200 WHERE id = 2;
-- La transazione 1 prova a bloccare id = 2
UPDATE accounts SET balance = balance - 100 WHERE id = 2;
-- La transazione 2 prova a bloccare id = 1
UPDATE accounts SET balance = balance + 200 WHERE id = 1;
Voilà! Ogni transazione è bloccata dall'altra. PostgreSQL prima o poi se ne accorge e tira fuori un errore di deadlock.
Come evitare i deadlock?
- Ordine sequenziale di accesso ai dati. Cerca sempre di accedere ai dati nello stesso ordine. Se la transazione 1 blocca prima la riga A e poi la riga B, anche la transazione 2 dovrebbe seguire la stessa logica: prima la riga A, poi la riga B.
-- Ordine corretto di accesso
BEGIN;
UPDATE accounts SET balance = balance - 100 WHERE id = 1;
UPDATE accounts SET balance = balance + 200 WHERE id = 2;
COMMIT;
Minimizza la durata delle transazioni. Le transazioni lunghe aumentano la probabilità di deadlock. Chiudi le transazioni il più velocemente possibile:
BEGIN, fai tutte le modifiche necessarie e subitoCOMMIT.Usa i livelli di isolamento delle transazioni. Se il tuo caso non richiede un isolamento super rigoroso dei dati, valuta di usare il livello di isolamento
READ COMMITTED. Ti aiuta a evitare alcuni lock.
Timeout e diagnostica dei lock
Ci sono strumenti per diagnosticare e prevenire i lock in PostgreSQL. Ad esempio, puoi impostare il tempo massimo di attesa per un lock:
SET lock_timeout = '5s'; -- Imposta il timeout del lock a 5 secondi
Se il lock dura più del tempo indicato, la transazione viene interrotta, così eviti di restare bloccato per sempre.
Monitorare i lock in PostgreSQL
Uno dei comandi utili per monitorare i lock è pg_locks. Ti mostra tutti i lock attivi nel sistema:
SELECT * FROM pg_locks;
Puoi vedere quali transazioni tengono i lock e quali stanno aspettando che vengano rilasciati. È super utile quando devi fare debug dei deadlock.
Particolarità di LOCK e lock manuale degli oggetti
Se vuoi gestire i lock manualmente, usa il comando LOCK:
LOCK TABLE orders IN ACCESS EXCLUSIVE MODE;
Attenzione: ACCESS EXCLUSIVE è il lock più forte, blocca qualsiasi altra operazione sulla tabella, anche SELECT. Usalo solo in casi particolari (tipo quando cambi la struttura della tabella).
Per bloccare singole righe durante una modifica usa SELECT ... FOR UPDATE:
SELECT * FROM accounts WHERE id = 1 FOR UPDATE;
Così sei sicuro che nessun'altra transazione possa modificare quelle righe fino alla fine della tua transazione.
Cosa ricordare sui lock?
I lock non sono il male, sono uno strumento. Aiutano a mantenere l'integrità dei dati, ma vanno usati con attenzione. Ecco qualche consiglio chiave:
- Non iniziare una transazione se non sai esattamente perché ti serve.
- Chiudi la transazione il prima possibile.
- Evita di accedere ai dati in ordine diverso.
- Usa i livelli di isolamento adatti al tuo caso.
Ora sei pronto a lavorare con lock e deadlock! Che pg_locks e la saggezza di ACID siano con te.
GO TO FULL VERSION