日期:2014-05-18  浏览次数:20684 次

关于事务锁机制的疑问.
测试过程如下,环境: WIN7 + SQL2008R2
SQL code

-- 建测试表ap,yx字段没索引.
create table ap
( Id int not null,
  yx char(8000) not null
  constraint pk_ap primary key clustered (Id)
)

-- 插入5笔记录: 40,41,42,43,44.
insert into ap select 40,'ap0405140_0'
insert into ap select 41,'ap0405140_1'
insert into ap select 42,'ap0405140_2'
insert into ap select 43,'ap0405140_3'
insert into ap select 44,'ap0405140_4'

select * from ap

Id          yx
----------- ---------------
40          ap0405140_0 
41          ap0405140_1 
42          ap0405140_2 
43          ap0405140_3 
44          ap0405140_4

-- 用dbcc ind()及dbcc page()获取记录与页的对应关系.
/*
  [Id]      PagePID
------------------------
   40         148
   41         174
   42         175
   43         192
   44         193    
*/

-- 1.测试read uncommitted时的锁
set transaction isolation level read uncommitted
begin tran
  update ap set yx='ap0405140_16' where yx like '%3'
--> 执行计划: Clustered Index Scan, 锁范围: PagePID 192 IX锁
rollback tran

-- 2.测试read committed时的锁
set transaction isolation level read committed
begin tran
  update ap set yx='ap0405140_16' where yx like '%3'
--> 执行计划: Clustered Index Scan, 锁范围: PagePID 192 IX锁
rollback tran

-- 3.测试repeatable read时的锁
set transaction isolation level repeatable read
begin tran
  update ap set yx='ap0405140_16' where yx like '%3'
--> 执行计划: Clustered Index Scan, 锁范围: PagePID 148 IU锁,175 IU锁,174 IU锁,193 IU锁,192 IX锁.
rollback tran

-- 4.测试serializable时的锁
set transaction isolation level serializable
begin tran
  update ap set yx='ap0405140_16' where yx like '%3'
--> 执行计划: Clustered Index Scan, 锁范围: PagePID 148 IU锁,175 IU锁,174 IU锁,193 IU锁,192 IX锁.
rollback tran


问题: 此处故意用Clustered Index Scan的方式,为何repeatable read和serializable时,
需要对非192的页面(192是最后需更新的页面)加IU锁?这样的机制未免也太严格了点,与之前的理解有些出入.

------解决方案--------------------
探讨
测试过程如下,环境: WIN7 + SQL2008R2
SQL code

-- 建测试表ap,yx字段没索引.
create table ap
( Id int not null,
yx char(8000) not null
constraint pk_ap primary key clustered (Id)
)

-- 插入5笔记录: 40,41,42,43,44.
inser……

------解决方案--------------------
由于走的是scan
1和2并不是没有锁,是加完锁,但释放了
3和4则保持锁到commit或roll
如果多加点数据行,3,4可能就不是页了,是表级的x或u


------解决方案--------------------
1,2也锁的,但读完资料就释放锁了

3会将碰到资料都锁起来直到交易结束,但无法防止新增资料

4如果不设索引键锁定范围的话,一旦被它碰到的资料,就完全锁了,插都插不进
------解决方案--------------------
可重复读--正在进行中的事务数据被上锁,即便是select语句执行完成.别的事务也不能修改这些数据,只能读取这些数据.从而在一个事务中两次读取结果一样...并发低于默认隔离级别.
SQL code

--可重复读
begin tran
select * from student where sno=2006001 
--读完了这一条共享锁不释放掉..我不释放掉共享..你两一个事务的排他就不能修改..排他共享不能共存
--他只有等到 这个事务commit掉后才会释放掉共享锁...所以事务2 进行修改就必须等待..
select * from student where sno=2006001  
 --这里和上一条的结果是完全一样的..因为上一条为该行上了共享不释放,其他修改不了.
commit tran   --这个时候才释放掉了行的共享锁..
begin tran2
update student set sname='大力水手'  --修改不掉..必须等待 上一个事务释放掉共享锁才能修改.
end tran2

但是,这个可重复读得事务隔离级别是保证了..读锁一只保持..重复读可以了..解决不了幻读

--可串行
没有锁现象. 在数据集上防止一个范围锁, 以防止其他用户在事务完成之前更新数据集或插入数据集内.这个级别限制最大.该级别作用与在事务内所有select 语句中所有表上设置holdlock相同. 也就是上了一个范围锁.最高级别,并发最低.如果你查询的数据量比较大..跟表锁没有什么区别了!

其工作原理: 事务中的锁被报纸在一个更高的级别上,利用索引产生 key range 锁,从而阻止对数据集插入.
为了防止向数据集插入行,数据集需要锁定,如果没有合适的索引,那么便又可能产生更高级别的锁,如 表锁,而非范围锁. 

--避免幻读,隔离级别可串行化..上范围锁
begin tran
select a from t where country='ch'
--读出a=5
select a from t where country='ch'
--读出a=5
commit tran

事务2 
insert into t values(6,'ch')  --由于支队'ch'加了范围锁..所以insert语句将等到事务1 提交后才执行

--如果事务1 查询的数据量比较大..就相当于..锁了整个表

------解决方案--------------------
有时候,系统会隐式地加一些锁,我们也可以利用一下的。
------解决方案--------------------