BENEDETTI ALESSANDRO Matricola :252805 PROGETTO DI TECNOLOGIA DELLE BASI DI DATI PARTE 2 Testo: Sviluppare semplici programmi che permettano di verificare i diversi livelli di isolamento previsti da SQL (e da JDBC) DBMS SCELTO: PostgreSQL 8.3.1 PREMESSE Facciamo una piccola introduzione di tipo teorico: I livelli di isolamento,da noi studiati in teoria,per evitare le anomalie dovute ad accessi concorrenti alle risorse sono 4: Read Uncommitted: si utilizza di solito per transazioni read only( questo per evitare di compromettere altre transazioni e basi di dati),con questo livello di isolamento la transazione non emette lock in lettura e quando legge non rispetta i lock imposti da altre transazioni. A questo livello sono possibili tutte le anomalie, tranne la perdita di aggiornamento. Read Committed: Per effettuare le letture richiede lock Condivisi,quindi(dovendo le scritture di altre transazioni rispettare il 2PL stretto),con questo livello di isolamento la transazione non leggerà mai dati intermedi transienti,quindi si eviterà la perdita di aggiornamento e la lettura sporca. Repeatable read: Con questo livello viene applicato il 2PL stretto anche con i lock di lettura, di conseguenza anche i lock di lettura verranno rilasciati solo alla fine della transazione,dopo l eventuale abort o commit. A questo livello si evitano quindi ogni genere di anomalie tranne l inserimento fantasma. Serializable: Applicando il 2PL stretto per tutti i lock e applicando anche lock di predicato,ogni anomalia viene evitata. Isolamento in Postgresql In PostgreSQL, per ogni transazione si può richiedere uno qualsiasi dei livelli di isolamento sopra specificati. Ma internamente questo DBMS offre solo 2 livelli di isolamento, ovvero Read Committed e Serializable. Di conseguenza,selezionando Read Uncommitted,si ottiene di default Read Committed, e,selezionando repeatable read si ottiene Serializable ;questo è piuttosto importante perché in taluni casi si otterrà un livello di isolamento maggiore di quanto realmente chiesto. Questo,ovviamente, è in accordo con gli standard SQl,I quali specificano solo quali fenomeni non devono manifestarsi per un determinato livello di isolamento, e questo è sicuramente garantito dall implementazione di protezione di Postgresql. La motivazione che sta dietro a tutto ciò è quella di rendere compatibile un architettura di controllo multiversione (offerta da postgresql) con I livelli di isolamento standard prima descritti. Read Committed Isolation Level in PostgreSQL Read Committed è il livello di isolamento di default in PostgreSQL. Quando una transazione viene eseguita con questo livello di isolamento, una query di SELECT vede solo I dati di transazioni che hanno fatto il commit prima che la query fosse iniziata; non può quindi leggere dati da transazioni che non hanno effettuato il commit o da transazioni che effettuano cambiamenti nel corso dell esecuzione. (ovviamente però la select vede I cambiamenti effettuati dalla transazione medesima,anche prima di effettuare la commit). In pratica una interrogazione vede lo stato del Database all istante in cui la query è iniziata;ovviamente due interrogazioni consecutive possono vedere dati differenti,se altre transazioni modificano dati ed effettuano il commit durante l esecuzione della prima query.(vedi anomalie studiate) UPDATE, DELETE, SELECT FOR UPDATE, e SELECT FOR SHARE si comportano come la select per cercare le tuple di interesse:troveranno solo tuple I cui dati sono stati modificati da transazioni che hanno effettuato la commit. Tuttavia se una riga è stata aggiornata (o cancellata o bloccata) da un'altra transazione concorrente,al momento
dell accesso,in questa situazione si attenderà lo sblocco della tupla( quindi in 2PL stretto,si attenderà il commit o rollback della transazione concorrente). Se la prima transazione che vuole effettuare l effettua il Roll-Back, I suoi effetti sono annullati e la seconda transazione può lavorare sui dati originali. Se la prima transazione che vuole effettuare l effettua il commit, la seconda transazione ignorerà la tupla se è stata cancellata, altrimenti lavorerà sulla riga appena aggiornata. E possible quindi vedere uno stato inconsistente per una transazione di aggiornamento. Qeusto perché non viene usato il 2PL stretto per le operazioni di lettura. Serializable Isolation Level in PostgreSQL Questo livello provvede il massimo isolamento per la transazione. Questo livello emula infatti l esecuzione seriale della stessa; comunque applicazioni che usano questo livello devono essere preparate a ritirare transazioni dovute ad un fallimento di serializzazione. Quando una transazione si trova a questo livello di isolamento,un interrogazione di SELECT vede solo I dati già salvati sul DB da altre transazioni che hanno effettuato la COMMIT ;non vede mai altri dati non ancora commessi o cambiamenti commessi durante una transazione in esecuzione e concorrente alla stessa.(comunque l interrogazione vede gli effetti di aggiornamenti precedentemente compiuti dalla stessa transazione).questo è differente dal Read Committed dove la SELECT vede invece i dati come essi sono prima dell esecuzione della query. UPDATE, DELETE, SELECT FOR UPDATE, e SELECT FOR SHARE si comportano come una select nel cercare: troveranno le righe come appaiono all inizio della transazione. Tuttavia, se una riga è stata modificata da un altra transazione al momento,si attenderà il completamento o roll-back della transazione concorrente. Se la prima transazione effettua il roll back, I suoi effetti sono negati e la transazione serializable può continuare nella sua esecuzione. Ma se la prima transazione che effettua l (ed ha attualmente aggiornato la riga e non solo bloccata) allora la transazione serializable effettuerà il roll-back con tale messaggio ERROR: could not serialize access due to concurrent Perchè non può modificare o bloccare righe che sono cambiate da alter transazioni dopo che la transazione serializzabile è iniziata. -RISULTATI SPERIMENTALI- Provvederò quindi a testare i 2 livelli di isolamento concessi da postgresql relativamente ai 5 fenomeni principali di anomalia. Perdita di Aggiornamento Come ho precedentemente descritto postgresql offre solo 2 livelli di isolamento;la perdita di aggiornamento è comunque un anomalia che viene prevenuta da ogni livello di isolamento,quindi sia da Read Committed sia da Serializable. Read Committed In particolare andando a verificare il fenomeno,mediante utilizzo di thread concorrenti,ognuno associato ad una transazione e sincronizzati ad Hoc per permettere la sequenza di InterLeaving generatrice dell anomalia,possiamo osservare che utilizzando semplici query di select,l anomalia compare e non è evitata. Dobbiamo in PostgreSQL infatti modificare la sintassi del select,indicando il costrutto: FOR SHARE o FOR UPDATE,questo per far si che il DBMS effettui o meno il lock sulla risorsa ottenuta dal select. Effettuando il select for,la tupla risultante sarà bloccata e di conseguenza avremo il corretto funzionamento di READ COMMITTED e l anomalia sarà evitata. Questo perché quando la seconda transazione effettuerà il select for sullo stesso risultato della transazione precedente,la transazione sarà bloccata finchè la prima non avrà rilasciato il lock sulla tupla di interesse.con il corretto utilizzo del 2PL sarà quindi evitata questa anomalia.
T1:ISOLATION LEVEL = READ COMMITTED //la prima transazione è partita T2:ISOLATION LEVEL = READ COMMITTED //la seconda è partita for T1 Legge : 35 //la prima transazione effettua la lettura e blocca T2:eseguo la query//t2 prova ad eseguire la query ma trova la tupla locked e si mette in attesa T1 : Aggiorna il valore...//t1 effettua l aggiornamento //T1 facendo il commit effettua l unlock della tupla transazione2: T2: select for disponibilita from prodotti where id=2096 T2 Legge : 36 //ora la query di T2 è stata svolta transazione2: T2: select for disponibilita from prodotti where id=2096 T2 Legge : 37//perfetto, il valore è corretto! Viceversa non utilizzando il select for,l anomalia avrà luogo anche a livello di isolamento READ_COMMITTED, questo perché i lock non vengono gestiti in automatico (!). T2:ISOLATION LEVEL = READ COMMITTED//la seconda è partita T1:ISOLATION LEVEL = READ COMMITTED//la prima transazione è partita T1 Legge : 35//la prima transazione effettua la lettura e non blocca T2:eseguo la query transazione2: T2: select disponibilita from prodotti where id=2096 T2 Legge : 35//la seconda transazione effettua la lettura e non blocca transazione2: T2: select disponibilita from prodotti where id=2096 T2 Legge : 36//dopo l aggiornamento fatto da T2 il valore vale 36 T1 : Aggiorna il valore...//ora T1 aggiorna, ma non tiene conto dell aggiornamento di T2! T1 Legge : 36 //infatti dopo l ulteriore aggiornamento abbiamo l anomalia manifestata Serializable In questo caso se non usiamo i lock espliciti mediante le particolari forme di select o viceversa,l anomalia viene evitata e al momento di scrittura della seconda transazione concorrente viene sollevato l errore: ERROR: could not serialize access due to concurrent perché si accorge che il dato precedentemente letto è stato modificato da un altra transazione concorrente e quindi avvisa che non può procedere alla scrittura ( vedi parte relativa alla spiegazione funzionamento SERIALIZABLE in PostgreSQL).
Lettura Sporca Come ho precedentemente descritto postgresql offre solo 2 livelli di isolamento;la perdita di aggiornamento è comunque un anomalia che viene prevenuta da ogni livello di isolamento a partire dal Read Committed,quindi sia da Read Committed sia da Serializable. Testandolo sul READ_UNCOMMITED avrei dovuto ottenere l anomalia,ma come precedentemente detto,in postgresql tale livello di isolamento non viene contemplato,quello di default è difatti Read Committed. Read Committed In particolare andando a verificare il fenomeno,mediante utilizzo di thread concorrenti,ognuno associato ad una transazione e sincronizzati ad Hoc per permettere la sequenza di InterLeaving generatrice dell anomalia,possiamo osservare che l anomalia viene correttamente evitata anche senza utilizzo di select for. T2:ISOLATION LEVEL = TRANSACTION_READ_COMMITTED T1:ISOLATION LEVEL = TRANSACTION_READ_COMMITTED T1 Legge : 39 T1 : Aggiorna il valore... T1 Legge : 40 T2:tenterò di eseguire la query//t2 tenterà di eseguire la query ma troverà la risorsa bloccata,attenderà quindi l unlock T1 : abort//t1 fa l abort,annulla I suoi effetti e sblocca la risorsa Transazione2: T2: select disponibilita from prodotti where id=2096for T2 Legge : 39//T2 legge finalmente il dato, che è corretto. SERIALIZABLE Anche in questo caso il fenomeno viene evitato, il comportamento sarà leggermente diverso,poiché l anomalia sarà evitata,ma T2 leggerà il dato prima dell abort di T1. A livello teorico potrebbe sembrare errato, ma come ho precedentemente detto,a questo livello di isolamento una select vede una foto dei dati only committed all inizio della transazione stessa.ignorerà quindi il dato modificato e che sarà poi abortito. T1:ISOLATION LEVEL = TRANSACTION_SERIALIZABLE T2:ISOLATION LEVEL = TRANSACTION_SERIALIZABLE Transazione3: T1: select disponibilita from prodotti where id=2096 T1 Legge : 39 T1 : Aggiorna il valore... Transazione3: T1: select disponibilita from prodotti where id=2096 T1 Legge : 40 T2:tenterò di eseguire la query transazione4: T2: select disponibilita from prodotti where id=2096 T2 Legge : 39 T1 : abort//l abort di T1 è avvenuto ugualmente dopo il commit di T2 ma il risultato è comunque corretto
Riscriviamo il concetto per precisione: Quando una transazione si trova a questo livello di isolamento,un interrogazione di SELECT vede solo I dati già salvati sul DB da alter transazioni che hanno effettuato la COMMIT ;non vede mai altri dati non ancora commessi o cambiamenti commessi durante una transazione in esecuzione e concorrente alla stessa.(comunque l interrogazione vede gli effetti di aggiornamenti precedentemente compiuti dalla stessa transazione).questo è differente dal Read Committed dove la SELECT vede invece i dati come essi sono prima dell esecuzione della query. Letture Inconsistenti Questa anomalia avviene quando una transazione effettua 2 letture,che dovrebbero essere uguali ma che potrebbero risultare differenti a causa di interferenze di altre transazioni. Di conseguenza tale anomalia dovrebbe presentarsi a livello READ_COMMITTED E UNCOMMITTED ma non a livello Repeatable Read e Serializable. Vediamo i risultati sperimentali: Read Committed In particolare andando a verificare il fenomeno,mediante utilizzo di thread concorrenti,ognuno associato ad una transazione e sincronizzati ad Hoc per permettere la sequenza di InterLeaving generatrice dell anomalia,possiamo osservare che l anomalia si presenta e T1 legge 2 valori diversi e quindi inconsistenti. Questo avviene utilizzando nella seconda transazione una interrogazione Select standard o con l utilizzo della select or. T1:ISOLATION LEVEL = TRANSACTION_READ_COMMITTED T2:ISOLATION LEVEL = TRANSACTION_READ_COMMITTED Transazione3: T1: select disponibilita from prodotti where id=2096 T1 Legge : 39 T2:tenterò di eseguire la query transazione4: T2: select disponibilita from prodotti where id=2096 T2 Legge : 39 transazione4: T2: select disponibilita from prodotti where id=2096for T2 Legge : 40 Transazione3: T1: select disponibilita from prodotti where id=2096for T1 Legge : 40//anomalia,precedentemente aveva letto 39!
SERIALIZABLE In questo caso il fenomeno viene evitato ma facciamo attenzione: la prima transazione evocherà un eccezione dovuta in PostgreSQl ad un concorrenziale se per errore effettuiamo una select for (il dbms penserà infatti che nella seconda lettura poi vogliamo aggiornare e genererà la conseguente eccezione dovuta ad aggiornamenti concorrenti) T1:ISOLATION LEVEL = TRANSACTION_SERIALIZABLE T2:ISOLATION LEVEL = TRANSACTION_SERIALIZABLE T1 Legge : 11 T2:tenterò di eseguire la query transazione2: T2: select disponibilita from prodotti where id=2096for T2 Legge : 11 transazione2: T2: select disponibilita from prodotti where id=2096for T2 Legge : 12 T1 Legge : 11//anomalia corretta!t1 legge sempre e solo 11 (caso di inopportuno utilizzo di select for ) T2:ISOLATION LEVEL = TRANSACTION_SERIALIZABLE T1:ISOLATION LEVEL = TRANSACTION_SERIALIZABLE Transazione3: T1: select disponibilita from prodotti where id=2096 T1 Legge : 42 T2:tenterò di eseguire la query transazione4: T2: select disponibilita from prodotti where id=2096for T2 Legge : 42 transazione4: T2: select disponibilita from prodotti where id=2096for T2 Legge : 43 //questo avviene se per errore la seconda lettura di T1 è una select for [ERRORE IN THREAD]: nome Transazione3:, tipoerrore org.postgresql.util.psqlexception: ERROR: could not serialize access due to concurrent T1 : abort Per precisione il motivo di questa eccezione era stato precedentemente spiegato e verrà qui riportato: Ma se la prima transazione che effettua l (ed ha attualmente aggiornato la riga e non solo bloccata) allora la transazione serializable effettuerà il roll-back con tale messaggio ERROR: could not serialize access due to concurrent Perchè non può modificare o bloccare righe che sono cambiate da altre transazioni dopo che la transazione serializzabile è iniziata.
Aggiornamento fantasma (GHOST UPDATE) Questa anomalia avviene quando 2 transizioni effettuano scritture concorrenziali su più risorse,avremo bisogno quindi di transizioni leggermente più complesse per testarla.di conseguenza tale anomalia dovrebbe presentarsi a livello READ_COMMITTED E UNCOMMITTED ma non a livello Repeatable Read e Serializable. Vediamo i risultati sperimentali: Read Committed In particolare andando a verificare il fenomeno,mediante utilizzo di thread concorrenti,ognuno associato ad una transazione e sincronizzati ad Hoc per permettere la sequenza di InterLeaving generatrice dell anomalia,possiamo osservare che l anomalia si presenta. In particolare ho ipotizzato questo vincolo di integrità: ovvero che la somma dei tre valori sia 30.E facile verificare che T1 veda una somma uguale a 31 mentre nel database a fine commit questa è sempre stata di 30. Questo avviene sia con la select normale che con le varianti for o for share. Questo è proprio causato dai vincoli del livello di isolamento che non blocca in lettura con il 2PL stretto. T2:ISOLATION LEVEL = READ_COMMITTED T1:ISOLATION LEVEL = READ COMMITTED T1:la somma dei 3 valori deve essere: 30 T1 Legge x : 10 transazione2: T2: select disponibilita from prodotti where id=2097 T2 Legge y: 10 Transazione1: T1: select disponibilita from prodotti where id=2097 T1 Legge y : 10 transazione2: T2: select disponibilita from prodotti where id=2098 T2 Legge z: 10 T2 : Aggiorna il valore di y : 9 T2 : Aggiorna il valore di z : 11 Transazione1: T1: select disponibilita from prodotti where id=2098 T1 Legge z : 11 T1 : questa è la somma da me calcolata :31//ecco presentarsi l anomalia SERIALIZABLE In questo caso il fenomeno viene correttamente evitato,la motivazione,già precedentemente espressa verrà qui riepilogata: T1:ISOLATION LEVEL = SERIALIZABLE T1:la somma dei 3 valori deve essere: 30 T2:ISOLATION LEVEL = SERIALIZABLE T1 Legge x : 10 transazione2: T2: select disponibilita from prodotti where id=2097for T2 Legge y: 10 Transazione1: T1: select disponibilita from prodotti where id=2097
T1 Legge y : 10 transazione2: T2: select disponibilita from prodotti where id=2098for T2 Legge z: 10 T2 : Aggiorna il valore di y : 9 T2 : Aggiorna il valore di z : 11 Transazione1: T1: select disponibilita from prodotti where id=2098 T1 Legge z : 10 T1 : questa è la somma da me calcolata :30//perfetto!anomalia evitata! Quando una transazione si trova a questo livello di isolamento,un interrogazione di SELECT vede solo I dati già salvati sul DB da altre transazioni che hanno effettuato la COMMIT ;non vede mai altri dati non ancora commessi o cambiamenti commessi durante una transazione in esecuzione e concorrente alla stessa.(comunque l interrogazione vede gli effetti di aggiornamenti precedentemente compiuti dalla stessa transazione).questo è differente dal Read Committed dove la SELECT vede invece i dati come essi sono prima dell esecuzione della query. INSERIMENTO FANTASMA (PHANTOM) Il Phantom è un anomalia molto insidiosa ed è evitabile solo con l ultimo livello di isolamento,quindi Serializable. Avviene quando si valuta in una transazione un dato aggregato per 2 volte, e nel mezzo delle due avviene un inserimento. Vediamo a livello sperimentale : READ COMMITTED Come prevedibile a tale livello di isolamento l anomalia si verifica,purtroppo la protezione non è abbastanza: T1:ISOLATION LEVEL = TRANSACTION_READ_COMMITTED T2:ISOLATION LEVEL = TRANSACTION_READ_COMMITTED Transazione1: T1: select SUM(disponibilita) as result from prodotti p where nome='cacciavite' T1 Legge : 17123 T2:INSERT INTO prodotti (id,codice, nome, descrizione, prezzo, disponibilita)values (8132,'jdbc34', 'Cacciavite', 'cacciavite utile', 500, 1000); T2 inserito Transazione1: T1: select SUM(disponibilita) as result from prodotti p where nome='cacciavite' T1 Legge : 18123//attenzione,a causa dell inserimento fantasma vengono letti 2 valori diversi
SERIALIZABLE Grazie al lock su predicato(nel nostro caso nome= Cacciavite ) l anomalia viene evitata con successo dall ultimo livello di protezione;ricordiamo inoltre che affinchè tutta la tabella non sia bloccata sarebbe opportuno impostare un indice secondario sull attributo nome. In questo modo il lock di predicato bloccherà solo le tuple caratterizzate da tale valore per tale attributo: T2:ISOLATION LEVEL = TRANSACTION_SERIALIZABLE T1:ISOLATION LEVEL = TRANSACTION_SERIALIZABLE Transazione1: T1: select SUM(disponibilita) as result from prodotti p where nome='cacciavite' T1 Legge : 17123 T2:INSERT INTO prodotti (id,codice, nome, descrizione, prezzo, disponibilita)values (8132,'jdbc34', 'Cacciavite', 'cacciavite utile', 500, 1000); T2 inserito Transazione1: T1: select SUM(disponibilita) as result from prodotti p where nome='cacciavite' T1 Legge : 17123//anomalia perfettamente evitata! In allegato il codice usato