[align=center]SQL2005中的事务与锁定(八)[/align]
全文: http://blog.csdn.net/happyflystone/archive/2009/11/05/4771647.aspx
    在前面一篇我说了锁的行锁与页锁之间的是与非,锁升级、动态锁、死锁,加上第六篇的一些理论加实例,基本上锁的相关知识这一个阶段结束了,这一篇我们来学习一下2005的新特性行版本控制器,顺便说说锁定提示及锁超时设定,其实这个锁定提示在前面已经经常提及了,只是我们没有细说。10、行版本控制综述
行版本控制是SQLSERVER2005保证数据完整及一致的新机制。我们前面提到并发模型有两种:悲观与乐观并发,而行版本控制是乐观并发下的一种保障数据完整及一致的新技术。行版本控制和前面提及的锁定机制不大一样,它保障了写的进程与读的进程间不会阻塞,并且在保证不读到未提交的数据下又提升了数据库的并发能力,然而我们要注意的是乐观并发下写的进程还是会获取排它锁定,上面提及的一些锁定模式、锁定时间及管理死锁的方式都适用于它。
SQLSERVER2005使用行版本控制的隔离有两个:RCSI 和 SI。RCSI是相对无阻塞的已提交读模式,所谓相对无阻塞是相对于传统的已提交读模式,在这种模式下写进程不会阻塞读进程,读进程不会设置共享锁定,而是使用行版本控制读取语句级的一致性的数据,简单的说就是任何读都可以得到语句开始那一时刻最近的已经提交数据。快照隔离(SI)可以使任何读进程读取到交易级的一致性数据,简单的说就是任何读进程都可以读取到交易开始时已提交数据。
SQLSERVER2005如何做到写不阻塞读的呢?一旦启用RCSI或SI后数据库开始在tempdb中存储所有已经修改过的记录副本(记录版本,以后我们直接称行版本),同时保证在只要有进程需要这些数据就会一直维持这些行版本,所以tempdb又被我们称为版本存储区。很显然的是启用行版本控制后tempdb得需要更多的空间来管理行版本,所以如果你的数据库使用了行版本管理,一定要管理好tempdb。行版本如何存储在版本存储区我下面再说。
好,整体上来说我们应该有这样一个概念:已经提交的数据存储在当前数据库,而当数据修改前的数据被复制到tempdb中,那么它们之间如何联系呢?这儿我们引入另一个术语:XSN,注意哦,区别于LSN哦。XSN称为事务序列号,新行通过这个XSN和tempdb里的行版本之间保持联系,是不是有点指针味道呀,哈哈,同时我们要注意了,新行XSN指向行版本区的某一旧行,同时这一旧行可能包含指向列旧的数据行XSN,SQLSERVE通过这个链表可以访问到正确的版本。
说了这么多,感情这个行版本管理后好处多多呀,至少增强了并发能力吧,可是在更改当前数据库使用行版本管理前还是要三思而行:首先,增加了tempdb的负担,这种负担不仅仅是空间上的。接着,维护旧版的数据行必然会降低更新操作的能力,不管有没有读进程存在,只要有更新存在数据库就得为此付出代价。第三,增加的并发能力使得每一个读进程都得付出额外的开销来访问刚才我们提到的XSN链表找到合适行版本。最后我们说阻塞是不能完全避免的,就是在这种乐观模式下写写还是阻塞的。在后续我们模拟在SI下的更新冲突。
    有人要问了RCSI和SI之间有什么差别呢?其实RCSI和SI行为上基本类似的,都是可以在当前数据锁定的前提下读取到当前数据已经提交的早期版本,它们的主要差别有二:一,行版本记录在行版本区保存的时效。此话怎么理解呢,我在前面说到RCSI是语句级的而SI是事务级的,这就是直接导致数据行版有多久的关键。二,RCSI是已经提交读的无阻塞的变种,而SI是存在阻塞的。下面我们会说道说道这两种行为。
11、行版本区
    SQLSERVER2005只要开启快照,所有更新和删除就会生成已经提交的行版本,而这些行版本是保存在行版本区,即tempdb数据库的数据分页上,随时保障快照的查询需要,换句话说只要有查询需要,行版本区数据就存在。SQLSERVER2005有一个清理线程,常规好像是一分钟就进行一次回收,对于SI隔离下的查询行版本保存事务结束,对于RCSI隔离下的查询行版本一直保存到当前查询语句结束。
    这儿提到tempdb,得稍微提提这个tempdb,tempdb也是记录日志的,并不是好多人认为的不记录,它的日志是为了临时对象上的事务回滚,记住只能回滚,不能恢复或重做,当然是是题外话,一带而过。
    Tempdb中有三种类型的对象:用户对象、内部对象、版本库。这个版本库的数据来源有三:一,重建索引或有快照级别的数据库上执行了DML(我们一会说这两个快照级别);二,触发器,这有别于2000哦,2005的伪表(deleted 、inserted)是由行版本产生的;三,活动结果集。

12、已经提交读快照隔离下读写行为
    RCSI我们一定要记住它是一个语句级的快照隔离级别,任何查询都可以查询到语句开始时最近的已经提交的数据。
    如何开启这个级别隔离我们前面已经写过了,对,用alter database dbname set read_comm.itted_snapshot on 就行,在运行这一命令要注意时不能用户在连接数据库,如果有人在使用数据库这个命令就会阻塞。这个命令有两个开关项:with nowait 和rollback来避免阻塞和终止任何数据库连接,大家可以查查联机从书。
    我在前面写隔离级别的事例时提到这个隔离级别和用锁定的已提交读具有一样的行为,下面我们用一个实例来看看:先修改当前当前库的READ_COMMITTED_SNAPSHOT为ON
ALTER DATABASE TESTCSDN
SET READ_COMMITTED_SNAPSHOT ON
GO
Exec sp_us_lockinfo
Go --test data and tablecreate table ta(id int,col varchar(10))
insert ta select 1 ,'a'
union all
select 2,'b'
union all 
select 3,'c'
go
查询一:begin tranupdate ta 
set col = 'd'
where id = 1waitfor delay '00:00:05' –故意加延时看看这个锁定是否影响查询二
exec sP_us_lockinfo –查看当前锁定情况,由图我们知道在表上有排它锁定
commit
 
查询二:begin tranwaitfor delay '00:00:01'—确保表上已经有排它锁定
select * 
from ta
where id = 1 -–行版本读到最近提交的数据
/*
id          col
----------- ----------
1           a
(1 行受影响)
*/
waitfor delay '00:00:05'—-保证查询一已经提交数据
select * 
from ta
where id = 1 – 查询到最新行版本数据
/*
id          col
----------- ----------
1           d
(1 行受影响)
*/
commit
    回顾一下以上过程,我们发现这个已提交快照和已提交锁定方式一样的行为,但是它比锁定模式有更强的并发能力 ,因为读写进程间不再阻塞。另外我们注意到没有,不需要在每一个会话中使用SET来设置选项就可以使用RCSI,也就是我们无需对应用程序作任何修改就可以从缺省的锁定方式的已提交读切换到快照方式的已提交读,从而降低阻塞带来的并发冲突。
12、快照隔离下的读写行为
    SI是SQLSERVER2005引入的一个新的隔离,要启用必须在两个地方同时启用:1、启用Allow_SNAPSHOW_ISOLATION;2、在会话中使用SET TRANSACTION ISOLATION LEVEL命令为每一个会话设置隔离。我们在前面说过它是一个乐观模式的隔离,类似于已提交读快照隔离,但是又有些差别。
    启用命令:
ALTER DATABASE DB_NAME SET ALLOW_SNAPSHOT_ISOLATION ON;
    当我们使用这个命令时,如果有活动连接时它不会像RCSI阻塞,但是如果有活动事务时还是会被阻塞。这个命令运行后数据的状态不会立即成ON状态 ,而是经历一个IN_TRANSITION_TO_ON的状态,这时数据库处于等待数据库中所有事务结束并开始为更新和删除产生版本数据,一旦在alter命令开始时已经进行的事务一结束,数据库就会进入ON状态。同理我修改为OFF时数据的库状态也会经历一个中间状态IN_TRANSITION_TO_OFF,等待活动的事务结束。一旦所有的活动事务结束数据库就会变为OFF状态。好下面我们来模拟启动过程,关闭的过程大家自己模拟吧。
 
我们模拟打开的过程:
查询一:开始一个事务,记住不要提交可回滚
BEGIN TRANUPDATE TA
SET COL = 'B' WHERE ID = 1
查询二:开启快照ALTER DATABASE DBlock SET ALLOW_SNAPSHOT_ISOLATION on;/*
查询一直进行中……
*/ 查询三:
exec sp_us_lockinfo
--大家可以看到当前数据处于中间态:IN_TRANSITION_TO_ON
 
查询四:
--我们模拟这时开启SI进行数据访问,看看会是什么结果
SET TRANSACTION ISOLATION LEVEL SNAPSHOTBEGIN TRAN
SELECT * FROM TA WHERE ID = 1
 /*
id          col
----------- ----------
消息3956,级别16,状态1,第4 行
快照隔离事务未能在数据库'dblock' 中启动,因为用于启用此数据库的快照隔离的
 ALTER DATABASE 命令尚未完成。数据库正在转换到挂起ON 状态。您必须等待,
直到ALTER DATABASE 命令成功完成。*/
接着,我们在查询一里 增加一句:
COMMIT;然后我们再运行查询三:     那好,我们通过上面的命令已经学会使用这个隔离级别。SI保证事务级的数据一致性,任何读操作都可以得到事务开始时最近已经提交的数据版本。下面我们再模拟一下查询快照数据:查询一:SELECT * FROM TA
/*
id          col
----------- ----------
1           B(1 行受影响)
*/
BEGIN TRANUPDATE TA
SET COL = 'C' WHERE ID = 1
 
WAITFOR DELAY '00:00:05'
exec sp_us_lockinfo
/*
  */
COMMIT查询二:
SET TRANSACTION ISOLATION LEVEL SNAPSHOTBEGIN TRAN
SELECT * FROM TA WHERE ID = 1
/*
id          col
----------- ----------
1           B(1 行受影响)
*/
waitfor delay '00:00:05'SELECT * FROM TA WHERE ID = 1
/*
id          col
----------- ----------
1           B(1 行受影响)
*/
commit tran
SELECT * FROM TA WHERE ID = 1
/*
 id          col
----------- ----------
1           C(1 行受影响)
*/
    还记得我在前面说过SI是有冲突阻塞(错误:3960)的哦, 下面我们模拟一下,这也是在提醒大家使用SI模式时一定要潜在的阻塞,好看下面的实例:查询一:
SET TRANSACTION ISOLATION LEVEL SNAPSHOT
BEGIN TRAN
SELECT * FROM TA where id = 1 WAITFOR DELAY '00:00:05'UPDATE TA
SET COL = 'c' WHERE ID = 1
 
/*消息3960,级别16,状态2,第9 行
快照隔离事务由于更新冲突而中止。您无法在数据库'dblock'中使用快照隔离来直接
或间接访问表'dbo.TA',以便更新、删除或插入已由其他事务修改或删除的行。请重
试该事务或更改update/delete 语句的隔离级别。*/查询二:WAITFOR DELAY '00:00:02'BEGIN TRAN
UPDATE TA
SET COL = 'd' WHERE ID = 1
commit tran
SELECT * FROM TA WHERE ID = 1
/*
 id          col
----------- ----------
1           d(1 行受影响)
*/。。14、锁定超时
SET LOCK_TIMEOUT n;
晕,这个看联机从书就行了,不能再写了。请大家继续关注我的blog: http://blog.csdn.net/happyflystone