A volte le transazioni sembrano i personaggi dei film di supereroi. Salvano i nostri database dal disastro in caso di crash, errori o problemi. Se stai lavorando su un task che richiede più operazioni che non puoi separare, le transazioni assicurano che tutto venga eseguito come un unico blocco. Vediamo come funziona con un esempio di gestione dei pagamenti.
Gestione dei pagamenti
Immagina la classica situazione: hai due conti bancari e vuoi trasferire soldi da uno all’altro. Non è solo un’operazione “con un click”. Devi essere sicuro di scalare i soldi dal conto giusto e aggiungerli all’altro. Qualsiasi errore può essere una catastrofe: o entrambi i conti restano invariati, oppure il saldo si sballa (tipo, i soldi spariscono o appaiono “dal nulla”).
Scenario: Trasferimento di soldi tra conti
Ecco il nostro codice. Leggilo con attenzione, come se fosse un messaggio da una galassia lontana:
-- Iniziamo la transazione
BEGIN;
-- Passo 1. Scalare i soldi dal conto mittente
UPDATE accounts
SET balance = balance - 100
WHERE account_id = 1;
-- Passo 2. Aggiungere i soldi al conto destinatario
UPDATE accounts
SET balance = balance + 100
WHERE account_id = 2;
-- Tutto ok? Allora salviamo le modifiche!
COMMIT;
Cosa è importante qui?
- Se al
Passo 1oPasso 2qualcosa va storto (tipo errore nella query, saldo insufficiente), la transazione può essere annullata conROLLBACK, e i dati restano come prima. COMMITgarantisce che le modifiche vengano applicate solo se TUTTI i passi vanno a buon fine.
Aggiungiamo il controllo del saldo
E se il mittente non ha abbastanza soldi per il trasferimento? Aggiungiamo un controllo sul saldo, così non lo mandiamo “in rosso” per sbaglio.
-- Iniziamo la transazione
BEGIN;
-- Prendiamo il saldo attuale del mittente
DO $$
DECLARE
current_balance NUMERIC;
BEGIN
SELECT balance INTO current_balance FROM accounts WHERE account_id = 1;
-- Controlliamo se ci sono abbastanza soldi
IF current_balance >= 100 THEN
-- Se ci sono abbastanza soldi, facciamo il trasferimento
UPDATE accounts
SET balance = balance - 100
WHERE account_id = 1;
UPDATE accounts
SET balance = balance + 100
WHERE account_id = 2;
-- Salviamo le modifiche
COMMIT;
ELSE
-- Se non ci sono abbastanza soldi, annulliamo
ROLLBACK;
RAISE NOTICE 'Saldo insufficiente per il trasferimento!';
END IF;
END $$;
Cosa c’è di interessante qui?
- Usiamo un blocco PL/pgSQL con controllo tramite
IF. Se il saldo è sotto la soglia, la transazione viene rifiutata e non cambia nulla. ROLLBACKannulla le modifiche se sono già iniziate (anche se qui non c’è ancora nulla da annullare, ma è buona pratica).
Questa lezione è dedicata a scenari reali di utilizzo delle transazioni, quindi ho deciso di portare un esempio dalla vita vera. Contiene una procedura memorizzata e usa PL-SQL. Penso che ormai tu abbia abbastanza esperienza per capire come funziona tutto qui. In futuro torneremo su PL-SQL e vedremo esempi ancora più tosti.
Aggiornamento massivo dei dati in una transazione
Creare una transazione è utile non solo per i trasferimenti di soldi. Mettiamo che hai il database di un e-commerce, dove ogni giorno decine di ordini cambiano stato, tipo da “in consegna” a “completato”. Come aggiorni tante righe tutte insieme, ma con la possibilità di annullare tutto se qualcosa va storto? Ovviamente, con una transazione.
Vediamo un altro scenario: aggiornamento degli stati degli ordini.
Ecco un esempio:
-- Iniziamo la transazione
BEGIN;
-- Passo 1. Aggiorniamo gli ordini con data di consegna passata
UPDATE orders
SET status = 'completato'
WHERE delivery_date < CURRENT_DATE;
-- Passo 2. Notifica di aggiornamento riuscito
RAISE NOTICE 'Tutti gli stati degli ordini sono stati aggiornati con successo.';
-- Applichiamo le modifiche
COMMIT;
E se qualcosa va storto?
C’è sempre la possibilità di errore. Tipo, ti sei dimenticato di mettere la condizione WHERE, e ora tutti gli ordini hanno lo stato completato. Per evitare queste situazioni, è importante chiudere la transazione o annullarla esplicitamente.
Vediamo uno scenario con annullamento:
-- Iniziamo la transazione
BEGIN;
-- Passo 1. Tentativo di aggiornare gli ordini senza condizione (ops, errore!)
UPDATE orders
SET status = 'completato';
-- Annulliamo la transazione per errore
ROLLBACK;
-- Ora gli ordini sono rimasti invariati
Aggiungiamo un po’ di “flessibilità” con SAVEPOINT
Non sempre serve annullare tutta la transazione. Se il tuo scenario ha più step, magari vuoi annullare solo una parte. Qui entra in gioco SAVEPOINT.
Ora il nostro scenario è questo: gestire più step con la possibilità di annullarne solo uno.
Immagina che stai gestendo un ordine con più step: scalare i prodotti dal magazzino, aggiornare lo stato dell’ordine, inviare la notifica al cliente. Se la notifica non parte, vuoi annullare solo quel passo, ma tenere le altre modifiche.
-- Inizia la transazione
BEGIN;
-- Passo 1. Scalare i prodotti dal magazzino
UPDATE products
SET stock = stock - 1
WHERE product_id = 101;
-- Salviamo il punto di rollback
SAVEPOINT step1;
-- Passo 2. Aggiorniamo lo stato dell’ordine
UPDATE orders
SET status = 'spedito'
WHERE order_id = 202;
-- Tentativo di inviare la notifica al cliente
SAVEPOINT step2;
-- Ops, errore durante la notifica!
ROLLBACK TO SAVEPOINT step2;
-- Decidiamo che è sicuro chiudere la transazione
COMMIT;
Conclusione
Le transazioni non sono solo uno strumento tecnico, sono una garanzia per l’integrità dei tuoi dati. Ti proteggono dall’“effetto domino”, quando un errore può buttare giù tutto il sistema. Ogni volta che fai più operazioni collegate, chiediti: “E se una di queste fallisce?” Se la risposta è “un disastro”, allora è il momento di usare una transazione. Ricorda: meglio perdere qualche minuto a scrivere una transazione che ore a recuperare dati dopo un crash. I tuoi utenti (e i tuoi nervi) ti ringrazieranno!
GO TO FULL VERSION