日期:2014-05-16  浏览次数:20472 次

对read committed隔离级别的全新认识:select语句能读取到,未来被其他事务修改过的数据???


关于锁,有很多可以学习的,比如,锁的粒度,锁的模式,锁的兼容性,以及隔离级别等等。


但是,具体到运行一个语句时,是怎么去获取到相应的锁的呢,比如,一个select语句,那么需要加S锁,可能是对记录,也可能是对页,也可能是对表,可能通过索引去找到记录,找到记录后,是一次获取所有的锁,还是一部分一部分的获取呢?

比如下面实验中,正好是一条记录占用1个数据页,一共查询32768条数据,那么到底是对32768个页都加上S锁,还是先对一些页加锁,查询出一部分数据,然后再对剩下的页加锁,再查询出一部分数据。


又比如,现在有2个会话,会话1运行select 语句来查询数据,假设查询数据需要大概耗时18秒,而会话2是一个update语句来更新同样的数据,大概需要耗时15秒,会话1先运行,会话2在一秒后运行,那么哪个语句会先运行完呢?

一般的想法,应该是会话1先返回,因为会话1的select语句,会对数据页加上共享锁的,既然加上了共享锁,那么会话2是后运行的,要对页加独占的X锁,肯定是等待的,但实际情况却完全不是这样。如果是还有10个select语句的会话,又会是那个先运行完呢?从实际执行的情况来看,是update语句先执行完的。


言归正传,实验代码


1、建表,插入数据,建索引,这段代码中批处理的次数是18次,也就是go 18,你可以根据实际情况修改,只要保证足够的数据量,使select语句运行需要20秒左右就可以

if OBJECT_ID('test') is not null
   drop table test
go

create table test(id int,v varchar(8000))

insert into test
select 1,replicate('a',8000) union all
select 2,replicate('o',8000) union all
select 3,replicate('c',8000) union all
select 4,replicate('d',8000) union all
select 5,replicate('e',8000) union all
select 6,replicate('f',8000) union all
select 7,replicate('g',8000) union all
select 8,replicate('h',8000) union all
select 9,replicate('i',8000) union all
select 10,replicate('j',8000) union all
select 11,replicate('k',8000) union all
select 12,replicate('l',8000)
go

insert into test
select *
from test
go 18

create index idx_test_id on test(id)

2、建立一个temp_lock表,用来存储sys.dm_tran_locks的信息,这个主要是用来监控语句执行时详细的锁信息

if OBJECT_ID('temp_lock') is not null
   drop table temp_lock
go

select 0 id,* into temp_lock
from sys.dm_tran_locks
where 1 = 2

接下来,需要新建3个查询,依次执行下面3个查询,也就是先执行会话1的代码,然后执行会话2的代码,然后执行会话3的代码,这种中间会有正常的时间间隔(不用同时执行)。


3、会话1是监控代码,注意这个代码是死循环,所以需要在会话2和会话3的代码,执行完后,取消执行查询,否则会一直运行下去

declare @i int

set @i = 1

while 1=1
begin
 insert into temp_lock
 select @i,*
 from sys.dm_tran_locks
 
 set @i = @i +1 
end 

4、查询2是select语句

select GETDATE()

begin tran

	select id,
		   %%lockres%%,   --文件id:页id:记录id
		   v
	from test
	where id =2

commit tran

select GETDATE()

5、查询3是update语句

select GETDATE()

update test
set v =replicate('x',8000)
where id = 2 

select GETDATE()