测试过程如下,环境: WIN7 + SQL2008R2-- 建测试表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 apId          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锁?这样的机制未免也太严格了点,与之前的理解有些出入.

解决方案 »

  1.   

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

  2.   

    可重复读--正在进行中的事务数据被上锁,即便是select语句执行完成.别的事务也不能修改这些数据,只能读取这些数据.从而在一个事务中两次读取结果一样...并发低于默认隔离级别.--可重复读
    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 查询的数据量比较大..就相当于..锁了整个表
      

  3.   

    为何"碰到"数据页都要加锁?仅是碰到,而非真的要update的页面.
    这样的话,repeatable read和serializable时有index scan的话,
    在高并发时挺麻烦的喔,这个机制有点奇怪耶,可以避免吗.
      

  4.   

    SQLSERVER加锁本来就是把所有可能更新到的页都加,有时候是很搞。
      

  5.   


    可重复读和可串行化 这两个级别对于真实环境应该是很少用的..
    隔离级别太高,可串行化没有合适的索引升级到表锁,并发就很差了!
    2005以上就分为两种并发控制了..
    悲观并发控制
    乐观并发控制--行版本控制技术 产生 快照隔离级别.
    行版本控制: 可以理解为."已经被更改的这些行的原始版本放到tempdb进行维护"这应该是也2005比2000
    更重视tempDB原因吧?支持行版本控制的事务隔离界别:
    1.新实现的已提交读隔离级别,使用行版本控制提供语句级的读取一致性.
    2.新快照隔离界别,提供事务级别的读取一致性当行版本控制隔离级别下运行事务读取数据时,读取操作不会获取正被读取的数据行上的
    共享锁, 因此不会阻塞正在死u该数据的事务,另外,锁定资源的开销随着所获取的锁的
    数量的减少而降至最低. 使用行版本控制的已提交读隔离级别和快照隔离都是在
    提供副本数据的语句级别或事务读取一致性.行版本控制新实现的已提交读隔离级别
    begin tran
    select a from t where country='ch' --这里读取数据并没有给读到的行上(共享锁)s,而是上了sch-s表级别模式锁,不需要叶锁或行锁.
    select a from t where country='ch' --这里和上面读到的数据有可能不同, 他读到的别的事务已提交的.
    commit tran
    begin tran2
    update t set a=0 where country='ch'
     --事务1 并不影响修改.因为他没有上共享锁,这里能顺利修改并提交.
    commit tran2
    --这样读和写就能并行操作!快照隔离,采用乐观方法:直到数据被修改时才获取数据上的锁,当数据行符合更新标准时,快照事务将验证数据行未被并发事务(在快照开始后提交)修改.如果数据行已在快照事务以为修改,则出现更新冲突,同事快照事务也将终止.更新冲突是由数据库引擎处理, 无法禁用更新冲突检查.
    --快照隔离级别
    begin tran
    q1=select count(1) from dbo.tname where country='usa' 
    --这个时刻有别的事务修改了数据并做了提交..q1 还是等于q2
    q2=select count(1) from dbo.tname where country='usa' --q1=q2
    commit tran--technet 上的例子
    use adventureworks;
    go
    alter database adventureworks set allow_snapshot_isolation on; --打开快照隔离级别
    go
    begin tran
    select employeeid,vacationhours from humanresources.employee where employeeid=4  --4,48 这条事务没有结束,不提交..
    select employeeid,vacationhours from humanresources.employee where employeeid=4 --第二个事务修改后并没有体现出来 这里还是 4,48, 可重复读.update humanresources.employee set vacationhours =vacationhours -8 where employeeid=4; --这里会报出更新冲突而终止,这里直接回滚掉了.所以快照隔离也将在这里终止..
    commit tran --这里没有事务了..已经回滚了..这一句也会报错..新开一个查询--修改 begin tran update humanresources.employee set vacationhours =vacationhours -8 where employeeid=4; commit tran  --修改并提交
    select employeeid,vacationhours from humanresources.employee where employeeid=4  --4,40 --修改完毕--快照隔离级别,在同一个事务里面读到的数据时一致的..但是有肯能会产生更新冲突!
      

  6.   

    可重复读和可串行化 这两个级别对于真实环境应该是很少用的..
    隔离级别太高,可串行化没有合适的索引升级到表锁,并发就很差了!
    2005以上就分为两种并发控制了..
    悲观并发控制
    乐观并发控制--行版本控制技术 产生 快照隔离级别.
    行版本控制: 可以理解为."已经被更改的这些行的原始版本放到tempdb进行维护"这应该是也2005比2000
    更重视tempDB原因吧?支持行版本控制的事务隔离界别:
    1.新实现的已提交读隔离级别,使用行版本控制提供语句级的读取一致性.
    2.新快照隔离界别,提供事务级别的读取一致性当行版本控制隔离级别下运行事务读取数据时,读取操作不会获取正被读取的数据行上的
    共享锁, 因此不会阻塞正在死u该数据的事务,另外,锁定资源的开销随着所获取的锁的
    数量的减少而降至最低. 使用行版本控制的已提交读隔离级别和快照隔离都是在
    提供副本数据的语句级别或事务读取一致性.行版本控制新实现的已提交读隔离级别
    begin tran
    select a from t where country='ch' --这里读取数据并没有给读到的行上(共享锁)s,而是上了sch-s表级别模式锁,不需要叶锁或行锁.
    select a from t where country='ch' --这里和上面读到的数据有可能不同, 他读到的别的事务已提交的.
    commit tran
    begin tran2
    update t set a=0 where country='ch'
     --事务1 并不影响修改.因为他没有上共享锁,这里能顺利修改并提交.
    commit tran2
    --这样读和写就能并行操作!快照隔离,采用乐观方法:直到数据被修改时才获取数据上的锁,当数据行符合更新标准时,快照事务将验证数据行未被并发事务(在快照开始后提交)修改.如果数据行已在快照事务以为修改,则出现更新冲突,同事快照事务也将终止.更新冲突是由数据库引擎处理, 无法禁用更新冲突检查.
    --快照隔离级别
    begin tran
    q1=select count(1) from dbo.tname where country='usa' 
    --这个时刻有别的事务修改了数据并做了提交..q1 还是等于q2
    q2=select count(1) from dbo.tname where country='usa' --q1=q2
    commit tran--technet 上的例子
    use adventureworks;
    go
    alter database adventureworks set allow_snapshot_isolation on; --打开快照隔离级别
    go
    begin tran
    select employeeid,vacationhours from humanresources.employee where employeeid=4  --4,48 这条事务没有结束,不提交..
    select employeeid,vacationhours from humanresources.employee where employeeid=4 --第二个事务修改后并没有体现出来 这里还是 4,48, 可重复读.update humanresources.employee set vacationhours =vacationhours -8 where employeeid=4; --这里会报出更新冲突而终止,这里直接回滚掉了.所以快照隔离也将在这里终止..
    commit tran --这里没有事务了..已经回滚了..这一句也会报错..新开一个查询--修改 begin tran update humanresources.employee set vacationhours =vacationhours -8 where employeeid=4; commit tran  --修改并提交
    select employeeid,vacationhours from humanresources.employee where employeeid=4  --4,40 --修改完毕--快照隔离级别,在同一个事务里面读到的数据时一致的..但是有肯能会产生更新冲突!
      

  7.   

    那个是这样的 默认的隔离级别 是 读提交
    没问题吧
    读提交就是读完释放锁
    那么 u锁 是怎么一个状况呢
    u锁 可以理解为 s+x 锁
    但是 也不完全是 因为 u锁和u锁之间是不兼容的可以怎么理解
    所以 你在 读提交的状况下,读完就是释放了
    第二个解释清楚了吧第一个 比第二个 更简单 因为它不加读锁啊
    现在说第3个
    第三个 隔离级别是可重复读
    可重复读的话,那么就会 在事务完成前 是不会释放锁的。所以 前面2个虽然是被扫描过 ,但是还是又 u锁残留在那边
    没问题吧
    第3 和 第4 在你这边是一样的
    为啥呢
    第三个 和第四个 从 联机文档的 隔离级别 看 差别 就是 幻读
    你这个例子 是不会产生幻读的,所以也没问题了
      

  8.   

    repeatable read的意思是可重复读,以我的测试案例,我认为只有锁住PagePid=192的那个页,就可实现重复读了!实在不知为何要把执行计划中涉及到的页都锁起来,这样未免太严格.
      

  9.   

    repeatable read 
    按道理来说他是不会锁表的.
    为什么锁住了!应该是因为你where条件跟的是like '%' !如果where条件你
    写成 WHERE yx='ap0405140_3',估计锁就不会升级到更高级别了!
    测试了一下:
    set transaction isolation level repeatable read
    begin tran
      update ap set yx='ap0405140_16' where yx='ap0405140_3'--新建一个窗口,做insert 
    begin tran
    insert into ap select 47,'ap0405140_5' --这一句是没有问题的..但是如果你上面用like '%'  这一句是插入不进去的!