Transaccions
Per què són necessàries les transaccions?
Imagineu el procés d'una transferència bancària: s'ha de treure diners d'un compte i afegir-los a un altre. Sense transaccions, si el sistema falla després de treure els diners però abans d'afegir-los al compte destí, els diners es perden. Una transacció garanteix que les dues operacions són atòmiques: o es fan les dues, o no és fa cap.
sequenceDiagram
participant A as Compte origen (100€)
participant DB as Base de dades
participant B as Compte destí (0€)
Note over DB: BEGIN TRANSACTION
A->>DB: UPDATE: saldo = saldo - 50
Note over DB: A ara te 50€
Note over A,B: *** Aquí falla el sistema sense transaccions ***
DB->>B: UPDATE: saldo = saldo + 50
Note over DB: COMMIT
Note over A,B: A te 50€, B te 50€ (consistent)
Sense transaccions, si el sistema falla entre els dos UPDATEs, el compte origen perd 50€ que mai arriben al destí. Amb una transacció, el ROLLBACK automàtic restaura el compte origen als 100€ originals.
BEGIN, COMMIT i ROLLBACK
-- Oracle NO necessita BEGIN per iniciar una transacció:
-- la primera instrucció DML la inicia automàticament
UPDATE comptes SET saldo = saldo - 50 WHERE id_compte = 1;
UPDATE comptes SET saldo = saldo + 50 WHERE id_compte = 2;
-- Confirmar
COMMIT;
-- Desfer (torna al darrer COMMIT o al començament de la sessió)
ROLLBACK;
Autocommit
Per defecte, la majoria de SGBD estan en mode autocommit: cada instruccio DML es confirma automàticament quan acaba. Aixo vol dir que UPDATE clients SET email = 'error' (sense WHERE) confirmaria el canvi immediatament sense possibilitat de ROLLBACK.
- PostgreSQL: Autocommit activat per defecte en psql. Per desactivar-lo:
\set AUTOCOMMIT off. - MySQL/MariaDB: Autocommit activat per defecte.
SET autocommit = 0;per desactivar-lo. - SQL Server: Autocommit activat per defecte. Cal
BEGIN TRANSACTIONexplícit per iniciar una transacció. - Oracle: Les instruccions DML no fan autocommit; cal un
COMMITexplícit. Les instruccions DDL (CREATE,ALTER,DROP,TRUNCATE) fan unCOMMITimplícit automàticament. En SQL*Plus es pot activar ambSET AUTOCOMMIT ON.
Bones pràctiques
Per a operacions crítiques (càrregues massives, migracions, actualitzacions globals), sempre englobeu les operaciones en una transacció explícita. Aixo us permet verificar els resultats amb SELECT antes de confirmar amb COMMIT.
SAVEPOINT
Un SAVEPOINT és un punt de control dins d'una transacció. Permet fer ROLLBACK fins a aquell punt sense desfer tota la transacció.
BEGIN;
INSERT INTO comandes (id_client, estat) VALUES (1, 'pendent');
SAVEPOINT sp_comanda;
INSERT INTO linies_comanda (id_comanda, id_producte, quantitat, preu_unitari)
VALUES (LASTVAL(), 5, 2, 29.99);
-- Suposem que aquí hi ha un error (producte inexistent, etc.)
ROLLBACK TO SAVEPOINT sp_comanda;
-- Desfem la linia de comanda però mantenim la capçalera
-- Podem intentar una inserció corregida
INSERT INTO linies_comanda (id_comanda, id_producte, quantitat, preu_unitari)
VALUES (LASTVAL(), 3, 2, 19.99); -- Producte correcte
COMMIT;
-- Hem confirmat la comanda amb la linia corregida
-- La transacció s'inicia implícitament amb la primera DML
INSERT INTO comandes (id_client, estat) VALUES (1, 'pendent');
SAVEPOINT sp_comanda;
INSERT INTO linies_comanda (id_comanda, id_producte, quantitat, preu_unitari)
VALUES (comandes_seq.CURRVAL, 5, 2, 29.99);
-- Suposem que aquí hi ha un error
ROLLBACK TO SAVEPOINT sp_comanda;
-- Desfem la línia però mantenim la capçalera de la comanda
INSERT INTO linies_comanda (id_comanda, id_producte, quantitat, preu_unitari)
VALUES (comandes_seq.CURRVAL, 3, 2, 19.99); -- Producte correcte
COMMIT;
Nivells d'aïllament
Els nivells d'aïllament controlen fins a quin punt una transacció veu els canvis que estan fent altres transaccions concurrents. Hi ha un compromís entre consistència i rendiment: més aïllament → més consistència → menys concurrencia.
Anomalies de concurrencia
| Anomalia | Descripció |
|---|---|
| Lectura bruta (Dirty read) | Llegim dades d'una transacció no confirmada que potser farà ROLLBACK |
| Lectura no repetible | Rellegim una fila i el valor ha canviat (una altra transacció la va modificar i confirmar) |
| Lectura fantasma | Rellegim un rang de files i n'han aparegut o desaparegut noves |
Nivells estàndard SQL
| nivell | Dirty Read | Non-Repeatable Read | Phantom Read |
|---|---|---|---|
| READ UNCOMMITTED | Possible | Possible | Possible |
| READ COMMITTED | Impossible | Possible | Possible |
| REPEATABLE READ | Impossible | Impossible | Possible |
| SERIALIZABLE | Impossible | Impossible | Impossible |
-- Establir el nivell d'aïllament per a la sessió
SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
-- O per a una transacció específica
BEGIN ISOLATION LEVEL SERIALIZABLE;
-- ... operacions ...
COMMIT;
-- nivell per defecte a PostgreSQL: READ COMMITTED
-- (MVCC garanteix que READ COMMITTED no dona dirty reads)
-- Oracle suporta només READ COMMITTED (per defecte) i SERIALIZABLE
-- No existeixen READ UNCOMMITTED ni REPEATABLE READ a Oracle
-- Per a una transacció específica:
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
-- Per a la sessió:
ALTER SESSION SET ISOLATION_LEVEL = SERIALIZABLE;
ALTER SESSION SET ISOLATION_LEVEL = READ COMMITTED;
-- Oracle usa MVCC nativament: els dirty reads són impossibles
-- fins i tot a READ COMMITTED (a diferencia d'altres motors)
Visualització de les anomalies de concurrència
READ UNCOMMITTED.
READ COMMITTED.
REPEATABLE READ en alguns motors.
REPEATABLE READ o SERIALIZABLE, T1 veu sempre el mateix "snapshot" de les dades que existia quan va iniciar la transacció. Cap modificació externa afecta el que T1 llegeix.
AC0372/05/03 — Miniactivitat
RA5 · CA5.5, CA5.6
Demostreu el funcionament de les transaccions amb una simulació de transferència bancària:
- Creeu una taula
comptes(id_compte, titular, saldo)i inseriu 3 comptes amb saldo inicial de 1000€ cadascun. - Escriviu un script SQL que transfereixi 200€ del compte 1 al compte 2, usant una transacció explícita.
- Simuleu un error (per exemple, intenteu transferir a un compte inexistent) i verificeu que el ROLLBACK restaura el saldo original.
- Demostreu el comportament del SAVEPOINT transferint primer una part i usant ROLLBACK TO SAVEPOINT si la segona part falla.