SQL2005中的事务与锁定(八)- 转载
------------------------------------------------------------------------
-- Author : happyflystone
-- Date : 2009-10-26
-- Version: Microsoft SQL Server 2005 - 9.00.2047.00 (Intel X86)
-- Apr 14 2006 01:12:25
-- Copyright (c) 1988-2005 Microsoft Corporation
-- Enterprise Edition on Windows NT 5.2 (Build 3790: Service Pack 2)
-- 转载请注明出处,更多请关注:http://blog.csdn.net/happyflystone
-- 关键字:行版本控制器 RCSI SI 锁定提示 锁超时设定
------------------------------------------------------------------------
在前面一篇我说了锁的行锁与页锁之间的是与非,锁升级、动态锁、死锁,加上第六篇的一些理论加实例,基本上锁的相关知识这一个阶段结束了,这一篇我们来学习一下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隔离下的查询行版本保存到事务结束(这就是为什么在SI隔离下不会出现 不可重复读,因为在SI模式下整个事务都取的是事务开始那一刻的行版本数据),对于RCSI隔离下的查询行版本一直保存到当前查询语句结束(这就是为什么RCSI隔离下会出现 不可重复读,因为在RCSI模式下整个事务都取的是表里面最近一次提交的数据,例如事务A有两次查询,但在两次查询间数据被另一个事务B修改并提交了,那么事务A的两次查询的结果就会不一样,造成不可重复读)。
这儿提到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 table create table ta(id int,col varchar(10)) insert ta select 1 ,'a' union all select 2,'b' union all select 3,'c' go
查询一:
begin tran update ta set col = 'd' where id = 1 waitfor delay '00:00:05' –故意加延时看看这个锁定是否影响查询二 exec sP_us_lockinfo –查看当前锁定情况,由图我们知道在表上有排它锁定 commit
查询二:
begin tran waitfor 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,也就是我们无需对应用程序作任何修改就可以从缺省的锁定方式的已提交读切换到快照方式的已提交读,从而降低阻塞带来的并发冲突。
13、快照隔离下的读写行为
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 TRAN UPDATE 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 SNAPSHOT BEGIN 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 TRAN UPDATE TA SET COL = 'C' WHERE ID = 1 WAITFOR DELAY '00:00:05' exec sp_us_lockinfo /*
*/ COMMIT
查询二:
SET TRANSACTION ISOLATION LEVEL SNAPSHOT BEGIN 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 行受影响) */
13、锁定提示(LOCK HINTS)
隔离级别是会话级别的,在会话内内对持有锁、阻塞 、锁的生命周期产生影响。然而,必要时我们使用表级锁定提示来改变这种默认锁定行为,但是我们一定要注意这种操作不当会影响并发性能。
我们一定要记住使用锁定提示是表级提示,因为我们一般是在From子句中使用wth指定。另外SQLSERVER2005推荐使用With(Locktype),不带with语法尽量不要使用。
下面枚举一下锁定提示的关键字(申明太晚了,我直接复制了以前收藏的):
锁提示使用示例:
SELECT au_lname FROM authors WITH (NOLOCK) 锁定提示 描述 HOLDLOCK 将共享锁保留到事务完成,而不是在相应的表、行或数据页不再需要 时就立即释放锁。它等同于SERIALIZABLE,只不过仅作用于表级。 NOLOCK 不发出共享锁,并且不要提供排它锁。当此选项生效时,可能会读取未提交的事务或一组在读取中间回滚的页面。有可能发生脏读。仅应用于 SELECT语句,显然相当于未提交读级别。 PAGLOCK 在通常使用单个表锁的地方采用页锁。 READCOMMITTED 用与运行在提交读隔离级别的事务相同的锁语义执行扫描。默认情况 下,SQL Server 2000 在此隔离级别上操作。 READPAST 跳过锁定行。此选项导致事务跳过由其它事务锁定的行(这些行平常 会显示在结果集内),而不是阻塞该事务,使其等待其它事务释放在这 些行上的锁。READPAST锁提示仅适用于运行在提交读隔离级别的事 务,并且只在行级锁之后读取。仅适用于SELECT语句。 READUNCOMMITTED 等同于NOLOCK。 REPEATABLEREAD 用与运行在可重复读隔离级别的事务相同的锁语义执行扫描。 ROWLOCK 使用行级锁,而不使用粒度更粗的页级锁和表级锁。 SERIALIZABLE 用与运行在可串行读隔离级别的事务相同的锁语义执行扫描。 等同于HOLDLOCK。 TABLOCK 使用表锁代替粒度更细的行级锁或页级锁。在语句结束前,SQL Server 一直持有该锁。但是,如果同时指定 HOLDLOCK,那么在事务结束之前,锁将被一直持有。 TABLOCKX 使用表的排它锁。该锁可以防止其它事务读取或更新表,并在语句或事务结束前一直持有。它和TABLOCKG与XLOCK连用的效果一样。 UPDLOCK 读取表时使用更新锁,而不使用共享锁,并将锁一直保留到语句或事务的结束。UPDLOCK的优点是允许您读取数据(不阻塞其它事务)并在以后更新数据,同时确保自从上次读取数据后数据没有被更改。消除转换类型的死锁重要技术。 XLOCK 提示SQLSERVER在语句使用的全部数据上使用排它锁,并一直保持到 事务结束时。可以使用 PAGLOCK或TABLOCK指定该锁,这种情况下 排它锁适用于适当级别的粒度
14、锁定超时
SET LOCK_TIMEOUT n;
晕,这个看联机从书就行了,不能再写了。