# Two-phase locking

Il two-phase locking (2PL), locking a due fasi in italiano, è un protocollo per il controllo della concorrenza utilizzato nel campo dei database e dell'elaborazione delle transazioni per garantire la serializzabilità di una schedule di transazioni. Questo protocollo è basato sui lock, applicabili da una transazione ad un dato (a diversi livelli di granularità: attributo, tupla, relazione, database), i quali bloccano l'accesso da parte di altre transazioni ai dati fino alla terminazione della transazione che li detiene.
Questo protocollo assume che ogni transazione in esecuzione preveda due fasi principali:
* fase ascendente: tutti i lock sulle risorse necessarie all'esecuzione dell'intera transazione vengono richiesti, non è possibile rilasciare alcun lock in questa fase;
* fase discendente: vengono rilasciati i lock senza possibilità di acquisirne dei nuovi fino alla fine della transazione.
I tipi di lock utilizzati da protocollo base sono due: esclusivo (binario) e condiviso (multiplo). Protocolli successivi a quello base possono utilizzare più tipi di lock. L'uso di alcune varianti di 2PL porta con sé il problema del deadlock risultante dall'attesa circolare che si può verificare fra le transazioni e i dati bloccati con i lock.

## Lock per l'accesso ai dati

Un lock è un oggetto di sistema associato ad una risorsa condivisa come una tupla o una relazione di una base di dati. Nei database l'acquisizione di un lock per l'accesso a dei dati può essere necessario ad una transazione per leggere/scrivere i dati stessi. Un corretto uso di questo strumento previene l'esecuzione di transazioni che svolgono operazioni in conflitto e che porterebbero la base di dati in uno stato inconsistente.
I due principali tipi di lock utilizzati sono:
* lock di scrittura (lock esclusivo), acquisito da una transazione per un certo dato al fine di poter svolgere un'operazione di scrittura (ovvero inserimento, modifica, cancellazione);
* lock di lettura (lock condiviso), acquisito da una transazione per un certo dato al fine di poter svolgere un'operazione di lettura.
Le richieste di lock sono inviate dalle transazioni al gestore della concorrenza il quale in base allo stato della risorsa interessata valuta se concedere il lock oppure far aspettare la transazione. Questa scelta è fatta consultando la seguente tabella:
| | Libero | R-lock | W-lock |
| R-lock | Sì/R-lock | Sì/R-lock | No/W-lock |
| W-lock | Sì/W-lock | No/R-lock | No/W-lock |
| Unlock | No/Libero | Sì/Dipende | Sì/Libero |
Questa tabella mostra sulla prima colonna le richieste possibili delle transazioni, sulla prima riga i possibili stati di una risorsa e all'interno delle caselle se la richiesta è stata accettata dal gestore della concorrenza e il rispettivo stato successivo della risorsa.
Nel caso in cui la risorsa sia nello stato di R-lock e la transazione richieda un unlock, ovvero di sbloccare la risorsa, quest'ultima diventa libera solo se non c'erano altre R-lock che la tenevano bloccata, infatti lo stato di R-lock è condiviso quindi più di una transazione può richiederlo e la risorsa diventa effettivamente libera solo quando tutte le risorse che la bloccavano in lettura la liberano. Per questo motivo solitamente si usa un contatore che tiene traccia di quante transazioni sono in read-lock su una risorsa, che viene incrementato dopo ogni R-lock e decrementato dopo ogni unlock. La risorsa diventa effettivamente libera solo quando tale contatore è a zero.

## Strict 2PL

La versione standard del locking a due fasi non riesce a prevenire il conflitto da lettura sporca, ovvero il fatto che un dato possa essere modificato da una transazione $t_{1}$, letto nella sua versione modificata da una transazione $t_{2}$ e riportato alla sua versione originaria nel caso che $t_{1}$ esegua un abort. Il two-phase locking stretto (strict 2PL) è una versione di locking a due fasi che previene questo tipo di anomalia garantendo che tutti gli unlock delle risorse vengano eseguiti solo dopo l'eventuale commit o abort della transazione; in questo modo se una transazione è in W-lock su una certa risorsa e abortisce, prima di liberare il lock esegue il rollback di tutte le modifiche. Questa versione di 2PL è quella maggiormente adottata nei DBMS commerciali.

## Esempio di transazioni concorrenti con il 2PL

Di seguito due transazioni che rispettano il 2PL, ma non il 2PL stretto:
| $t_{1}$ | $t_{2}$ |
| lock(p1) | |
| read(p1) | |
| | lock(p1) |
| write(p1) | |
| lock(p2) | |
| unlock(p1) | |
| | read(p1) |
| | write(p1) |
| | lock(p2) |
| | unlock(p1) |
| read(p2) | |
| write(p2) | |
| unlock(p2) | |
| | read(p2) |
| | write(p2) |
| | unlock(p2) |
Come si può vedere dalla tabella, sia in T1 che in T2 i lock e gli unlock sono acquisiti e rilasciati secondo il 2PL, ma non secondo il 2PL stretto; la transazione $t_{1}$ infatti compie un unlock sulla risorsa p1 prima del commit, dopo aver rilasciato p1 infatti esegue le azioni di read e write su p2.