In questa lezione ci concentriamo sugli errori tipici che capitano quando si lavora con le transazioni, e su come evitarli. Fidati, anche il più figo degli SQL-master ogni tanto si dimentica di mettere COMMIT! Ti diamo qualche dritta per far sì che gli errori nelle transazioni diventino una rarità.
Purtroppo (o per fortuna), i database non sono castelli magici dove tutto funziona sempre alla perfezione. Gli errori con le transazioni sono piuttosto frequenti, soprattutto per chi è alle prime armi. Vediamoli nel dettaglio.
Comando COMMIT o ROLLBACK dimenticato
Dimenticare di chiudere una transazione è proprio un "classico". Immagina un ristorante dove ordini da mangiare ma il cameriere si dimentica di portarti lo scontrino. Nel mondo di PostgreSQL questo vuol dire che il database "rimane appeso" in stato di transazione, tiene occupate risorse e blocca altre operazioni.
Esempio di errore:
BEGIN;
UPDATE accounts SET balance = balance - 100 WHERE account_id = 1;
-- Ops! Abbiamo dimenticato di aggiungere COMMIT o ROLLBACK.
Quando la transazione "rimane appesa", il blocco delle risorse può estendersi a tutta la tabella. Se l'amministratore del database si accorge che la situazione è grave, può chiuderla in modo "forzato". Ma meglio non arrivarci.
Come evitarlo?
- Chiudi sempre la transazione in modo esplicito:
COMMIToROLLBACK. - Usa strumenti client che ti ricordano automaticamente delle transazioni rimaste appese.
- Se la transazione non è chiusa e riavvii l'applicazione, il database farà
ROLLBACKin automatico, ma non sempre è comodo per lo stato del sistema.
Uso del livello di isolamento sbagliato
Scegliere il livello di isolamento può sembrare una formalità noiosa, ma in realtà è fondamentale per evitare anomalie. Per esempio, se usi READ UNCOMMITTED per un'operazione finanziaria importante, potresti leggere dati "sporchi" che poi vengono annullati.
Esempio:
SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
BEGIN;
-- Leggiamo dati che vengono modificati da un'altra transazione
SELECT balance FROM accounts WHERE account_id = 1;
-- Un'altra transazione fa ROLLBACK e i tuoi dati non sono più validi.
Come evitarlo?
- Decidi quanto sono importanti i dati per la tua app.
- Usa
READ COMMITTEDnella maggior parte dei casi per evitare letture "sporche". - Applica livelli di isolamento più severi
REPEATABLE READ,SERIALIZABLEper operazioni dove è fondamentale evitare cambiamenti o dati fantasma.
Conflitto tra transazioni e lock
A volte due o più transazioni cercano di modificare gli stessi dati. In questo caso PostgreSQL blocca una delle due finché l'altra non finisce. Questo può portare a una situazione di deadlock (blocco reciproco).
Esempio di errore:
-- Prima transazione
BEGIN;
UPDATE accounts SET balance = balance - 100 WHERE account_id = 1;
-- Seconda transazione
BEGIN;
UPDATE accounts SET balance = balance + 100 WHERE account_id = 1;
-- In attesa della prima transazione...
Se entrambe le transazioni tengono risorse di cui l'altra ha bisogno, nasce un deadlock. PostgreSQL lo rileva, termina una delle transazioni con errore e mostra il messaggio:
ERROR: deadlock detected
Come evitarlo?
- Segui sempre lo stesso ordine nelle operazioni dentro le transazioni.
- Riduci al minimo il tempo di esecuzione della transazione per diminuire la probabilità di lock.
- Usa il livello di isolamento
SERIALIZABLEsolo quando è davvero necessario.
Errore con SAVEPOINT
SAVEPOINT è uno strumento fantastico per fare rollback parziali, ma se lo usi male può creare confusione. Per esempio, se ti dimentichi di liberare il savepoint (RELEASE SAVEPOINT), puoi avere lock inutili o errori.
Esempio di errore:
BEGIN;
SAVEPOINT my_savepoint;
UPDATE accounts SET balance = balance - 100 WHERE account_id = 1;
ROLLBACK TO SAVEPOINT my_savepoint;
-- Dimenticato di liberare il SAVEPOINT!
Come evitarlo?
- Assicurati di eliminare il
SAVEPOINTse non ti serve più. - Evita di creare troppi savepoint per non complicare le query.
Incompatibilità delle transazioni con sistemi esterni
Immagina che una transazione in PostgreSQL provi a interagire con sistemi esterni: invio di notifiche, aggiornamento di API ecc. Se qualcosa va storto con il sistema esterno, fare rollback delle modifiche diventa difficile.
Esempio:
BEGIN;
UPDATE accounts SET balance = balance - 100 WHERE account_id = 1;
-- Impossibile inviare la notifica: email-server non risponde.
COMMIT; -- Le modifiche sono salvate, ma la notifica non è stata inviata.
Come evitarlo?
- Se puoi, isola le operazioni che coinvolgono sistemi esterni.
- Usa tabelle intermedie o code di task per coordinare le azioni con i sistemi esterni.
Errori dovuti a transazioni troppo grandi
Le transazioni grandi, che includono tante operazioni, sono più soggette a errori: lock, timeout e deadlock.
Esempio:
BEGIN;
-- Diverse migliaia di operazioni di update
UPDATE orders SET status = 'completato' WHERE delivery_date < CURRENT_DATE;
COMMIT; -- Può richiedere molto tempo.
Come evitarlo?
- Dividi le transazioni grandi in più transazioni più piccole.
- Usa batch per aggiornare i dati.
- Riduci al minimo la quantità di dati modificati in una sola transazione.
Controllo degli errori dimenticato
Non tutte le query SQL in una transazione vanno sempre a buon fine. Per esempio, se una delle operazioni dà errore, tutta la transazione va in stato di fallimento.
Esempio:
BEGIN;
UPDATE accounts SET balance = balance - 100 WHERE account_id = -1; -- Errore: account_id non esiste.
UPDATE accounts SET balance = balance + 100 WHERE account_id = 2;
COMMIT; -- Non viene eseguito a causa dell'errore.
Come evitarlo?
- Controlla sempre il risultato di ogni operazione.
- Gestisci gli errori nelle tue query o nel codice client.
Fraintendimento del comportamento di ROLLBACK
Tanti sviluppatori pensano che ROLLBACK annulli le modifiche e riporti tutto allo stato iniziale. In realtà ROLLBACK funziona solo all'interno della transazione corrente.
Esempio di fraintendimento:
UPDATE accounts SET balance = balance - 100 WHERE account_id = 1;
ROLLBACK; -- Errore! Non funziona perché l'operazione non era in una transazione.
Come evitarlo?
- Ricorda:
BEGINè tuo amico, senza di luiROLLBACKè inutile. - Metti sempre le operazioni critiche dentro una transazione.
GO TO FULL VERSION