Le singole istruzioni INSERT
, UPDATE
e DELETE
sono atomiche nel senso che o hanno successo totale (su tutte le righe coinvolte) o falliscono totalmente senza alcun effetto sul database.
È possibile combinare più istruzioni in una singola transazione atomica. Ad esempio, in Oracle o in MySQL:
START TRANSACTION;
DELETE FROM Book WHERE Publisher = 1;
DELETE FROM Publisher WHERE ID = 1
COMMIT;
Con questo script viene eliminato l’editore con ID pari a 1, ma prima vengono eliminati i libri pubblicati da esso. Racchiudendo il blocco tra START TRANSACTION
e COMMIT TRANSACTION
, si fa in modo di rendere tutto il blocco atomico: o avvengono con successo entrambe le istruzioni oppure in caso di errore tutto resta invariato. Con l’istruzione ROLLBACK
, invece, si forza il fallimento della transazione, lasciando il database allo stato consistente.
Livello di isolamento delle transazioni
A proposito delle transazioni, soprattutto se esse sono lunghe, è importante sapere quale livello di isolamento stiamo usando. Il livello di isolamento stabilisce come transazioni contemporanee si comportano rispetto ai dati. Ogni RDBMS ha un livello di isolamento di default e generalmente si può stabilire un livello differente per sessione o per transazione. Lo standard ANSI/ISO stabilisce quattro livelli di isolamento, generalmente implementati da tutti i RDBMS:
- Serializable. È il massimo livello di isolamento: ogni transazione, dall’inizio alla fine, non vede le modifiche fatte ai dati acceduti. Il vantaggio è che la transazione può lavorare sul database assumendo di essere la sola transazione in corso sul database. Un modo di realizzare questo livello è l’approccio ottimistico: ogni transazione lavora in isolamento, poi, se accadono problemi di concorrenza, la transazione che tenta di agire su un dato modificato da altre transazioni fallirà con un errore e con conseguente rollback. Lo svantaggio è che ci possono essere molte scritture fallite se ci sono tante transazioni che interessano gli stessi dati.
- Repeatable Read. Con questo livello si fa in modo che i dati letti durante la transazione in corso non possono essere modificati da altre transazioni per tutta la durata della transazione in corso. I vari RDBMS gestiscono questo livello utilizzando i lock in lettura sulle righe lette durante la transazione. L’unico problema che può succedere con questo livello consiste nel verificarsi delle cosiddette letture fantasma: se rieseguo la stessa query durante la transazione, potrei trovare righe in più di quelle che ho letto in precedenza, ma mai in meno o modificate. Lo svantaggio di questo livello è una penalizzazione delle prestazioni se ci sono molte transazioni concorrenti che agiscono sulle stesse tabelle. Questo è il livello di default per MySQL.
- Read Committed. Utilizzando questo livello, invece, si evitano i lock in lettura sulle tabelle che sono molto onerosi dal punto di vista prestazionale. Lo svantaggio è che, oltre al fenomeno delle letture fantasma, si verifica anche quello delle letture non ripetibili: in pratica, rieseguendo due volte la stessa
SELECT
nel corso di una transazione, potrei ottenere dati diversi se altre transazioni sono terminate nel tempo intercorso tra le due letture. Questo è il livello di default per Oracle e per Microsoft Sql Server. - Read Uncommitted. Questo è il livello più basso, in pratica nessun isolamento. Con questo livello si possono avere letture sporche: nella transazione corrente si possono leggere dati che qualche altra transazione sta scrivendo in quel momento senza aver ancora fatto
COMMIT
, quindi può capitare di leggere chiavi violate, dati inconsistenti, eccetera.
Ovviamente non esiste il livello migliore di isolamento, generalmente il livello di default è valido nella maggior parte dei contesti. Gli RDBMS supportano gli altri livelli per gestire casi particolari di utilizzo in concorrenza.