Questo articolo utilizza parte delle informazioni trovate in:
1.Introduzione
Una transazione รจ detta una sequenza di operazioni effettuate da una singola unitร logica di lavoro.
Se la transazione viene eseguita correttamente, viene eseguito il commit di tutte le modifiche dei dati apportate durante la transazione e tali modifiche diventano parte permanente del database. Se si verificano errori durante lโesecuzione della transazione ed รจ necessario annullarla o eseguirne il rollback, verranno cancellate tutte le modifiche dei dati.
Una transazione deve avere tutte le quattro proprietร chiave dellโacronimo ACID:
- Atomicitร : la transazione รจ indivisibile nella sua esecuzione e la sua esecuzione deve essere o totale o nulla, non sono ammesse esecuzioni parziali;
- Coerenza: quando inizia una transazione il database si trova in uno stato coerente e quando la transazione termina il database deve essere in un altro stato coerente, ovvero non deve violare eventuali vincoli di integritร , quindi non devono verificarsi contraddizioni (inconsistenza) tra i dati archiviati nel DB;
- Isolamento: ogni transazione deve essere eseguita in modo isolato e indipendente dalle altre transazioni, lโeventuale fallimento di una transazione non deve interferire con le altre transazioni in esecuzione;
- Durabilitร : una volta che una transazione abbia richiesto un commit work, i cambiamenti apportati non dovranno essere piรน persi.
2. Livelli di isolamento
Sono previsti quattro livelli di isolamento, qui elencati dal meno al piรน sicuro.
- Read uncommitted: consente transazioni in sola lettura, senza bloccare in lettura i dati, conseguentemente una transazione puรฒ leggere dati sporchi, perchรฉ potrebbero sparire se la transazione che li ha generati abortisce. Vedi approfondimento qui;
- Read committed: prevede il rilascio immediato dei dati in lettura, ritardando quelli in scrittura, conseguentemente previene il fenomeno delle letture sporche;
- Repeatable read: vengono bloccati sia i dati in lettura che quelli in scrittura ma solo sulle ennuple della tabella coinvolte. Questa modalitร previene la lettura dei dati quando altre transazioni stanno modificando gli stessi, ma non previene operazioni di INSERT, cioรจ dati che vengono aggiunti nella transazione corrente da altre transazioni, conseguentemente ottengo un numero diverso di righe lanciando la stessa query due volte allโinterno della stessa transazione. Queste letture vengono dette phantom reads;
- Serializable: il piรน alto livello di sicurezza delle transazioni, con lโutilizzo dei RANGE lock previene la lettura, la modifica e lโinserimento di nuovi dati fino a che la transazione corrente non รจ stata correttamente completata.
3. Tipologie di istruzioni
3.1 Transazioni con autocommit
La piรน semplice transazione รจ una singola istruzione di modifica dati, come la seguente:
UPDATE Autori
SET Nome = 'John'
WHERE IdAutori = 10
Questo tipo di transazione รจ chiamato Autocommit transaction, SQL Server esegue questa serie di operazioni:
- Scrive in un file di log cosa sta per fare;
- Effettua effettivamente lโistruzione di update;
- Scrive nel file di log che ha completato lโoperazione.
Se il server fallisce dopo che una transazione รจ stata scritta nel file di log. SQl Server utilizzerร tale log per eseguire un roll forward di tale transazione quando questa ricomincerร .
3.2 Transazioni esplicite
Per rendere le transazioni piรน utili dobbiamo inserire due o piรน istruzioni allโinterno di queste. Queste transazioni sono chiamate Explicit Transaction, per esempio:
BEGIN TRANSACTION
UPDATE Autori
SET Nome = 'John'
WHERE IdAutori = '10'
UPDATE Autori
SET Nome = 'Marg'
WHERE IdAutori = '8'
COMMIT TRANSACTION
Dato che la transazione รจ esplicita, abbiamo un BEGIN TRANSACTION
allโinzio del set di istruzioni ed un COMMIT TRANSACTION
alla fine.
- BEGIN TRANSACTION Contrassegna il punto di inizio di una transazione locale esplicita ed incrementa la funzione
@@TRANCOUNT
di una unitร . - COMMIT TRANSACTION: Contrassegna la fine di una transazione esplicita o implicita completata correttamente. Se il valore di
@@TRANCOUNT
รจ 1,COMMIT TRANSACTION
rende permanenti nel database tutte le modifiche dei dati apportate dallโinizio della transazione, libera le risorse mantenute attive dalla transazione e decrementa il valore di@@TRANCOUNT
a 0. Se il valore di@@TRANCOUNT
รจ maggiore di 1,COMMIT TRANSACTION
decrementa il valore di@@TRANCOUNT
di una sola unitร e la transazione rimane attiva.
Ogni istruzione allโinterno dei due comandi รจ considerato come una singola unitร logica di lavoro.
Se il sistema fallisce per un qualsiasi motivo dopo il primo update, nessun UPDATE verrร eseguito.
Il file di log conterrร un BEGIN TRANSACTION
ma nessun COMMIT TRANSACTION
.
Al posto del COMMIT
posso esplicitare un ROLLBACK TRANSACTION
, che esegue il rollback di una transazione implicita o esplicita fino allโinizio della transazione o fino a un punto di salvataggio.
Lโistruzione ROLLBACK TRANSACTION
consente di cancellare tutte le modifiche dei dati eseguite dallโinizio della transazione o fino a un punto di salvataggio, nonchรฉ di liberare le risorse utilizzate dalla transazione.
3.2.1 Stored Procedure
La maggior parte delle transazioni avverranno allโinterno di Stored Procedure. Consideriamo il seguente esempio:
CREATE PROCEDURE TranTest1
AS
BEGIN TRANSACTION
-- idAutori=10 esiste giร su DB
INSERT INTO Autori(idAutori, Nome, Cognome)
VALUES (
10
, 'Gates'
, 'Bill'
)
UPDATE Autori
SET Nome = 'Johnzzz'
WHERE idAutori = 10
COMMIT TRANSACTION
Il problema con questa Stored Procedure รจ che la transazione indicata non verifica che le operazioni siano andate a buon fine o meno, verifica solo che SQL Server non fallisca tra lโINSERT
e lโUPDATE
.
Lanciando questa SP, questa proverร ad inserire un record duplicato nella tabella Autori
, che fornirร un errore di violazione di chiave primaria; inoltre il messaggio avviserร che the statement has been terminated.
Il problema รจ che la transazione รจ ancora aperta, conseguentemente lโUPDATE
verrร eseguito correttamente e SQL Server eseguirร il COMMIT
della transazione, comportamento assolutamente non voluto.
Il modo corretto di scrivere questa SP รจ il seguente:
CREATE PROCEDURE TranTest1
AS
BEGIN TRANSACTION
INSERT INTO Autori (
idAutori
, Nome
, Cognome
)
VALUES (
10
, 'Gates'
, 'Bill'
)
IF @@ERROR <> 0
BEGIN
ROLLBACK TRANSACTION
RETURN 10
END
UPDATE Autori
SET Nome = 'John'
WHERE idAutori = 10
IF @@ERROR <> 0
BEGIN
ROLLBACK TRANSACTION
RETURN 11
END
COMMIT TRANSACTION
In questo modo abbiamo il controllo di ogni istruzione per verificare se questa fallisca o meno. Se lโistruzione in question fallisce (che posso verificare tramite il controllo che @@ERROR <> 0
) allora eseguiamo un ROLLBACK
di quanto eseguito e forniamo un RETURN
che esce dalla stored procedure.
Sottolineiamo ancora che se non controlliamo gli errori dopo ogni istruzione atomica allโinterno della transazione, potremmo eseguire impropriamente un COMMMIT
di tale transazione.
3.3 Transazioni implicite
Una nuova transazione viene avviata in modo implicito al termine di una transazione precedente, conseguentemente non devo scrivere nulla per indicare lโinizio di una transazione, ma tutte le transazioni vengono terminate in modo esplicito con unโistruzione COMMIT
o ROLLBACK
.
Questa รจ una modalitร di lavoro che deve essere impostata dal Database Engine utilizzato.
4. Transazioni innestate
SQL Server permette lโutilizzo di transazioni innestate: questo significa che una nuova transazione puรฒ iniziare anche se la precedente non รจ stata ancora completata.
Questo viene eseguito semplicemente innestando un BEGIN TRANSACTION
prima di un COMMIT
o ROLLABCK
; lโindicatore @@TRANCOUNT
indica il numero di transazioni attive in questo momento, quindi il livello di nesting.
Andiamo ora ad analizzare il comportamento dei comandi in base al livello di nesting a cui questi vengono lanciati
4.1 COMMIT
Il comando di COMMIT
ha due comportamenti diversi nel caso in cui questo sia lanciato nella transazione piรน esterna o meno.
Se viene lanciato nellโultima transazione, allora effettua le scritture effettive su disco, altrimenti si limita a diminuire il valore di @@TRANCOUNT
di uno.
Conseguentemente una transazione non รจ mai commitatta definitivamente fino a che lโultimo COMMIT non รจ stato lanciato.
4.2 ROLLBACK
Il comando di ROLLBACK
invece lavora independentemente dal livello di nesting a cui viene lanciato ed esegue un ROLLBACK di tutte le transazioni.
4.3 Lโasimmetria tra COMMIT e ROLLBACK
Anche se sembra controintuitivo, esiste una ottima motivazione per cui SQL Server lavora in questo modo: se un COMMIT
innestato effettuasse veramente le scritture su disco, tutti i ROLLBACK
esterni non potrebbero effettuare effettivamente il ripristino di questi cambiamenti in quanto questi sarebbero giร registrati in modo permanente.
Questa modalitร รจ detta asimmetria tra COMMIT E ROLLBACK. Consideriamo il seguente esempio:
-- @@TRANCOUNT = 0
BEGIN TRANSACTION
-- @@TRANCOUNT = 1
DELETE table_1
BEGIN TRANSACTION transaction_name
-- @@TRANCOUNT = 2
DELETE table_2
COMMIT TRANSACTION nested
-- Esegue solo una diminuzione di @@TRANCOUNT di uno
-- @@TRANCOUNT = 1
ROLLBACK TRANSACTION
-- @@TRANCOUNT = 0
SELECT * FROM table_2
In questo esempio possiamo notare che il comando di ROLLBACK
ripristina i valori della tabella table_2
anche se รจ stato eseguito un COMMIT su di essa.
4.4 SAVE TRANSACTION
I savepoint sono un meccaniscmo per eseguire il ROLLBACK solo di una porzione di una transazione.
I savepoint definiscono un punto nel codice in cui una transazione puรฒ ritornare se parte di una transazione viene annullato.
SQL Server permette di identificare un savepoint mediante il comando SAVE TRANSACTION
, che non aumenta il valore di @@TRANCOUNT
, inoltre un ROLLBACK
ad un savepoint non modifica il valore di @@TRANCOUNT
(a differenza di un ROLLBACK
ad una transazione).
Unโistruzione di ROLLBACK
deve esplicitare il nome del savepoint, altrimenti verrร eseguito il rollback dellโintera transazione.