Now that we got into isolation levels, it is time that we play with code to have deeper understanding and to identify issues related to isolation. Before we go ahead and jump into isolation levels there are pre-requisites that needs to explicitly mention.
- All tests are done on SQL Server 2008 (But one can have any flavors of SQL Server 200x or even SQL Server 7.0)
- For all tests there need to be a minimum of two Sessions. Isolation Levels protect data between Isolation Levels and not in same session. But in many cases I would be using more than 2 session to show different flavors of same.
- So, a minimum of 2 Sessions for testing Isolation Levels and third Session for running diagnostic Queries is needed.
- Before going to next isolation level Rollback all transactions from all sessions that are open.
With pre-requisites out of way, let us start it..
Pre-work before we start:
Create Database TestIsolationLevels
go
Use TestIsolationLevels
Go
Create table TestReadUnCommitted (Col1 Int, Col2 Char(100))
Insert into TestReadUnCommitted values(1,REPLICATE('A',100))
go
Read Un-Committed: Read data that is not committed.
In Session 1: Run below with out running Rollback / Commit Tran.
--Session 1
Begin Tran
Update TestReadUnCommitted
set
Col1 = Col1 + 100,
Col2 = Replicate('B',100)
--Rollback Tran
In Session 2: Run below query, we see that though row is being updated in previous session (Session 1), Session 2 can read that data that is currently being updated. Notice values they are post execution of Update statement. Earlier we had inserted 1 and AAAA… but Update statement updated these column values to 101 and BBBBBBB…..
With Read Uncommitted Isolation Level we are reading data that is on the fly or that is being updated at this moment. This is what is dirty read.
Set Transaction Isolation Level Read Uncommitted
Select * from TestReadUncommitted
Other flavour of dirty read is, leaving session to default isolation level (which is read committed) and use query hint (NOLOCK) to achieve Read Uncommitted isolation level. We do not need to run above command but has been written only to reiterate point and notice query hint in bold below . Using “Set Transactional Isolation Read Uncommitted” would set at session level and NOLOCK query hint would provide same purpose at query level.
Set Transaction Isolation level Read Committed
Select * from TestIsolationLevels.dbo.TestReadUncommitted with (nolock)
Read Committed: Read Data that is Committed else wait for data that is modified to be committed.
Before starting second example, let us Rollback all transactions from each of sessions but issuing Rollback command.
Post Rollback in
Session 1: Run below command without running Rollback / Commit Tran
--Session 1
Begin Tran
Update TestReadUnCommitted
set
Col1 = Col1 + 100,
Col2 = Replicate('B',100)
--Rollback Tran
In Session 2: Run below query, since it is read committed any reads of modified data are not possible and would only be possible post issuance of commit by first session. That is why it is always Read after commit. Session 2 will be in this wait state till transaction is committed in first session or till second session reaches timeout value.
This phenomena is what is termed as Blocking by most Database Administrators.
Set Transaction Isolation Level Read committed
Select * from TestReadUncommitted
Notice below there are no results and we see query is still being executed.
The moment first session commits data, Second session will be able to read data and provide result set. That is why it is termed as Read Committed (Read only after commit).
The other flavour for same if one’s session is in Read Uncommitted Isolation level is to provide a query locking hint
Set Transaction Isolation level Read UnCommitted
Select * from TestIsolationLevels.dbo.TestReadUncommitted with (UpdLock)
This , like before would force query to wait before Commit is issued by first session.
Problems with Read Committed Isolation Level: We have seen how Read Committed Isolation Level solves Dirty Read Inconsistency problem by disallowing sessions to read from a transaction that has modified data but not yet committed. Now we would see data consistency problems with Read Committed Isolation Level.
DO NOT RUN as of YET but just type them and keep it.
In Session 1 and type below query:
Set Transaction Isolation Level Read Committed
Begin tran
Select * from TestReadUnCommitted
Waitfor delay '00:01:00'
Select * from TestReadUnCommitted
Commit
Go to Session 2 and type below Query:
Set Transaction Isolation Level Read committed
begin tran
Update TestReadUncommitted
set Col1 = Col1 + 100,
Col2 = REPLICATE ('B',100)
Commit
Go to Session 3 and type below query:
Set Transaction Isolation level Read Committed
Begin Tran
Insert into TestReadUncommitted
Values(100, REPLICATE('Z',100) )
Commit
First Start running query in first Session followed by Second Session and then third Session. After 1 minute (that is what waitfor 00:01:00) come back to session 1 and see the result. Both select Statements though are in same transaction would return different results which is nothing but Non-Repetable Read inconsistency wrt to data that has been updated and Phantom data inconsistency wrt to new row (100, ZZZ…) that has been inserted.
See below snapshot of results from my select statement:
First Select statement from Session 1 has below result set
For second Select statement from Session 1 has below result set
Now if you review session 1 both Selects are in same transaction and there is data inconsistency between both select. Look at second select statement output more closely.
Row 1: 101 and BBBBBBBBBB……. => This is a non repeatable read data consistency problem as after first select statement, same row has been updated and during second select same row with updated values is read. i.e between 2 counts of employees, some employees changed name from something else to Cathy or from Cathy to something else.
Row 2: 100 and ZZZZZZZZZ…….. => This a Phantom data consistency problem as during first read this row was not even there and during second select this row appeared from no where. same as new employees who joined with name “Cathy” between reads of employee counts.
REPEATABLE READ: Once a transaction reads existing data, read data would never be changed in that transaction irrespective of how many times same select statement is issued.
Coming back to our old analogy where there was discrepancies of employee count, imagine I was smart enough to ask people if they were “Cathy” and all employees were disallowed to change their names (by locking them in a room :) ) till my count and recount is over then we solved data inconsistencies with non repeatable read. That is what Repeatable read Isolation does, it takes locks on all resources (rows, index key) that query has touched and will not release till transaction is complete. But it will be able to only lock resources (rows, keys) that are there in table (index) but not rows that would be inserted. So in repeatable read Isolation level Shared Lock on Select statements are not released till end of transactions.
Since our current data in table has 2 rows, I am truncating table and reverting back to single row by running below queries
Truncate table TestReadUncommitted
Insert into TestReadUnCommitted values(1,REPLICATE('A',100))
Now table has 1 Row which is 1, AAAAAA….
Now type below queries from each of Sessions but DO NOT EXECUTE THEM as of YET.
Session 1:
Set Transaction Isolation Level Repeatable Read
begin tran
Select * from TestReadUnCommitted
Waitfor delay '00:01:00'
Select * from TestReadUnCommitted
Commit
Session 2:
Set Transaction Isolation Level Read committed
begin tran
Update TestReadUncommitted
set Col1 = Col1 + 100,
Col2 = REPLICATE ('B',100)
Commit
Session 3:
Set Transaction Isolation level Read Committed
Begin Tran
Insert into TestReadUncommitted
Values(100, REPLICATE('Z',100))
Commit Tran
Post all sessions are ready, run query in Session 1 followed by Session 2 and then session 3. You will see Session 2 is stuck where as session 3 went through fine.
Session 1 when it read data first time during first select, it acquired Select locks that are not released till end of transaction (unlike in Read Committed where Select locks are released as soon as data read is over), so Session 2 could not modify data as it has been held by Session 1 but Session 3 went fine as it was not blocked by Session 1. Session 1 only locked those resources that is currently has accessed it did not prevent someone or some other session from inserting data. That is why it did not solve Phantom problems but it solved Non Repeatable Read data consistency problem.
Serializable:Last Isolation level serializable as you may already have guessed it would not allow any data inconsistencies.. ie. it will not allow Dirty Reads, Non Repeatable Reads and Phantoms.
Since our current data in table has 2 rows, I am truncating table and reverting back to single row by running below queries
Truncate table TestReadUncommitted
Insert into TestReadUnCommitted values(1,REPLICATE('A',100))
Let us type below queries in each of these sessions:
Session 1:
Set Transaction Isolation Level Serializable
begin tran
Select * from TestReadUnCommitted
Waitfor delay '00:01:00'
Select * from TestReadUnCommitted
Commit
Session 2:
Set Transaction Isolation Level Read committed
begin tran
Update TestReadUncommitted
set Col1 = Col1 + 100,
Col2 = REPLICATE ('B',100)
Commit
Session 3:
Set Transaction Isolation level Read Committed
Begin Tran
Insert into TestReadUncommitted
Values(100, REPLICATE('Z',100))
Commit Tran
Set Transaction Isolation level Read Committed
Begin Tran
Insert into TestReadUncommitted
Values(100, REPLICATE('Z',100))
Commit Tran
Let us run first Session 1 followed by Session 2 and followed by Session 3. You would notice that Session 2 and Session 3 would be in wait state and would not execute until session 1 is completed. This is the top most isolation level with not data inconsistencies between reads.
Both of them are consistent as none of data consistencies are allowed but at same time concurrency of system is low as it is only one select that executes at one time.
I know this is pretty long blog with lot of content, typing and concepts but I could not help, as splitting into multiple blogs would only increase complexity and it would be tough to write about it. My Plan is to create a application that shows different isolation levels.
As always, If you have any questions on the subject of Isolation Levels, Locking, Blocking that you would like to see addressed, feel free to post them as comments, and I would do my best to incorporate them along the way.
Until Next post
-Consult Guru Group