Concurrency Series: Why do I get blocking when I use Read Uncommitted isolation level or use NOLOCK hint?

Author: Sunil Agarwal
Reviewers: Kun Cheng, Chuck Heinzelman, Shaun Tinline-Jones, Sanjay Mishra, Kevin Liu

When using NOLOCK hint on the table or using read uncommitted isolation level, customers sometimes get surprised when they experience blocking. Let me explain this using the following example

Example: Concurrent DDL operation

-- create a simple table for our examples

create table basic_locking (c1 int, c2 int, c3 int)

go

-- insert 500 rows into the table

declare @i int = 0

while (@i < 500)

begin

insert into basic_locking values (@i, @i+ 100, @i+10000)

set @i += 1

end

-- In session-1, do an update in a transaction

-- It will acquire X lock on all the rows in the table

-- or possibly an X lock on the table.

begin tran

update basic_locking set c2 = 1000 where c1 = 1

-- script to show blocking and locks

select

t1.request_session_id as spid,

t1.resource_type as type,

t1.resource_database_id as dbid,

t1.resource_description as description,

t1.request_mode as mode,

t1.request_status as status

from sys.dm_tran_locks as t1

where t1.request_session_id = @@SPID

Here is the output of the locks taken using the following query. Since only one row is being updated, you see a X lock on the data row and the IX lock on the data page (i.e. 153)containing the row and IX lock on the table.

 

-- Session-2 use read uncommitted to access the data. Since the query –- is executed with NOLOCK hint or read uncommitted isolation level,

-- it will run without getting blocked

begin tran

-- As expected, this select will not block

-- and will return 100 which ‘dirty’ data

select top 1 c1 from basic_locking with (NOLOCK)

Now,let us execute the DDL operation in session-1 as follows:

-- In session-1, add a column to the table under the same

-- transaction

alter table basic_locking add c4 int

-- Session-2 use read uncommitted to access the data

-- This statement will block while waiting to to acquire

-- SCH-S lock on the table.

select c2 from basic_locking with (NOLOCK) where c1 = 1

-- use the following script to show blocking and locks

select

t1.request_session_id as spid,

t1.resource_type as type,

t1.resource_database_id as dbid,

t1.request_mode as mode,

t1.request_status as status,

t2.blocking_session_id

from sys.dm_tran_locks as t1 left outer join sys.dm_os_waiting_tasks as t2

ON t1.lock_owner_address = t2.resource_address

WHERE t1.request_status = 'WAIT'

Here is the output that shows that session -1 (SPID-51) is blocking session-2(SPID-54) on SCH-S lock request.

 

The reason for this blocking is that during the execution of a SQL statement, the SQL Server requires a schema stability lock (i.e. SCH-S) on objects referenced in the statement to protect against concurrent DDL. For the simple example above, if this lock was not taken, the question will be should the returned rows have 3 column or 4 columns? Or the rows that were returned before DDL operation committed show 3 columns while the remaining rows show 4 columns?

In fact, if you use RCSI mode or snapshot isolation level, the query will get blocked similarly. Since DDL is a not a common operation in production environment, this kind of blocking should happen rarely.

 

Thanks

Sunil Agarwal