在SQL Server标准的已提交读(READ COMMITTED)隔离级别下,一个读操作会和一个写操作相互阻塞。未提交读(READ UNCOMMITTED)虽然不会有这种阻塞,但是读操作可能会读到脏数据,这是大部分用户不能接受的。有些关系型数据库(例如Oracle)使用的是另一种处理方式。在任何一个修改之前,先对修改前的版本做一个复制[WX1] ,后续的一切读操作都会去读这个复制的版本,修改将创建一个新的版本。在这种处理方式下,读、写操作不会相互阻塞。使用这种行版本控制机制的好处,是程序的并发性比较高,但是缺点是用户读到的虽然不是一个脏数据,但是可能是个正在被修改马上就要过期的数据值[WX2] 。如果根据这个过期的值做数据修改,会产生逻辑错误。
[WX1]复制的内容保存在tempdb当中。
[WX2]假如读跟写同时进行,读到的不是现在正被修改的值,如果是读到正被修改的值那就是脏读了。读到的是修改前的值。但是这个值随时会过期。等到修改完就过期了。
有些用户可能为了更高的并发性而不在乎这种缺点,所以更喜欢Oracle的那种处理方法。为了满足这部分用户的需求,SQL Server 2005也引入了这种机制,来实现类似的功能。所以选取行版本控制隔离级别也可以成为消除阻塞和死锁的一种手段。
SQL Server有两种行版本控制,使用行版本控制的已提交读隔离(READ_COMMITTED_SNAPSHOT)和直接使用SNAPSHOT事务隔离级别。
- READ_COMMITTED_SNAPSHOT数据库选项为ON时,READ_COMMITTED事务通过使用行版本控制提供语句级读取一致性。
- ALLOW_SNAPSHOT_ISOLATION数据库选项为ON时,SNAPSHOT事务通过使用行版本控制提供事务级读取一致性。
下列示例可以说明使用普通已提交读事务,行版本控制的快照隔离事务和行版本控制的已提交读事务的行为差异。
实验1:Read Committed Isolation level
query1:事务1
query2:事务2
总结:
- 事务1中的读操作没有阻塞事务2中的写操作
- 事务2中的更新操作阻塞了事务1中后来的读操作,如下图所示:
- 事务1两次查询得到的数据分别是48跟40,两次获得的数据内容不一样。所以也成read committed为不可重复读。在read committed隔离级别中,只在语句级别加锁,当语句执行完以后自动释放锁。比如事务1中的第一次查询,虽然查询在事务中进行,并且事务没有提交,但是此时查询语句执行完以后在table上就找不到锁了。
实验2:Snapshot Isolation
此示例中,在快照隔离下运行的事务将读取数据,然后由另一事务修改此数据。快照事务不会被其他事务执行的更新操作所阻塞,它忽略数据的修改继续从版本化的行读取数据。也就是说,读取到的是数据修改前的版本。但是,当快照事务尝试修改已由其他事务修改的数据时,它将生成错误并终止。
query1:事务1,快照事务
query2:事务2
总结:
- 快照事务1的读操作没有阻塞普通事务2的读操作,但是阻塞了事务2的删除操作,如果在事务2中执行delete操作的话会报错:Employees cannot be deleted. They can only be marked as not current.Msg 3609, Level 16, State 1, Line 1The transaction ended in the trigger. The batch has been aborted.
- 普通事务2的更新操作,没有阻塞事务1的读操作,但是我们发现事务1中读到数据是事务2更新之前的内容。因为读取的是版本化中的行数据。
在上述实验中,我们发现下面两条语句使一起使用的,也就是首先允许数据库开启snapshot isolation,然后再将isolation level设定为snapshot。
--step1:启用快照隔离 ALTER DATABASE AdventureWorks SET ALLOW_SNAPSHOT_ISOLATION ON; GO --step2:设置使用快照隔离级别,前面没有设定是因为数据库默认的隔离界别就是Read Committed SET TRANSACTION ISOLATION LEVEL SNAPSHOT; GO
在执行完step1以前,我们可以在sys.databases中查看AdvantureWorks的snapshot_isolation_state和 is_read_committed_snapshot_on这两个属性:
SELECT name,snapshot_isolation_state, snapshot_isolation_state_desc, is_read_committed_snapshot_on FROM sys.databases where name='AdventureWorks';
查询结果如下图所示:
假如我们不执行step1,只执行step2,然后开启事务进行查询,会报如下错误:
Msg 3952, Level 16, State 1, Line 3
Snapshot isolation transaction failed accessing database 'AdventureWorks' because snapshot isolation is not allowed in this database. Use ALTER DATABASE to allow snapshot isolation.
执行完step1以后,我们再次查看sys.databases中的内容,发现snapshot_isolation_state由0变为1,如下图所示:
实验3:使用行版本控制的已提交读隔离(READ_COMMITTED_SNAPSHOT)
在此示例中,使用行版本控制的已提交读事务与其他事务并发运行。已提交读事务的行为与快照事务的行为有所不同。与快照事务相同的是,即使其他事务修改了数据,已提交读事务也将读取版本化的行。与快照事务不同的是,已提交读将执行下列操作:
query1:事务1
query2:事务2
总结:
- 在事务2修改数据之后,提交之前,事务1中读到的是快照数据,也就是事务2没有修改之前的值。
- 在事务2提交修改之后,事务1读到了修改之后的数据。并且事务1可以修改由其他数据修改了的数据。
结论
- 使用 sys.databases 目录视图可以确定两个行版本控制数据库选项的状态。
- 对用户表和存储在 master 和 msdb 中的某些系统表的任何更新都会生成行版本。
- 在 master 和 msdb 数据库中,ALLOW_SNAPSHOT_ISOLATION 选项自动设置为 ON,并且不能禁用。
- 在 master 数据库、tempdb 数据库或 msdb 数据库中,用户不能将 READ_COMMITTED_SNAPSHOT 选项设置为 ON。
- 从上面的测试可以看到,原先会发生阻塞的两个会话在使用行版本控制的隔离级别后,都不会遇到阻塞了。但是两种行版本控制的结果又有不同。可以用表1来总结。
表1 使用行版本控制隔离级别后的不同
会话1 |
会话2 |
结果 |
||||
A. 普通已提交事务 |
B. 使用快照隔离 |
C. 使用行版本控制的已提交读 |
||||
BEGIN TRAN 查询1 |
|
48 |
||||
|
BEGIN TRAN 修改1 |
成功 |
||||
|
查询1 |
40 |
||||
查询2 |
|
被阻塞 |
48 |
|||
|
COMMIT TRAN |
查询2返回40 |
|
|||
查询3 |
|
40 |
48 |
40 |
||
修改2 ROLLBACK TRAN |
|
成功 |
失败 |
成功 |
补充:如果要查看锁状态,可以使用如下两种方法:
本文转自xwdreamer博客园博客,原文链接:http://www.cnblogs.com/xwdreamer/archive/2012/07/30/2615357.html,如需转载请自行联系原作者