事务的隔离级别
SQL Server通过在锁资源上使用不同类型的锁来隔离事务。为了开发安全的事务,定义事务内容以及应在何种情况下回滚至关重要,定义如何以及在多长时间内在事务中保持锁定也同等重要。这由隔离级别决定。应用不同的隔离级别,SQL
Server赋予开发者一种能力,让他们为每一个单独事务定义与其他事务的隔离程度。事务隔离级别的定义如下:
- 是否在读数据的时候使用锁
- 读锁持续多长时间
- 在读数据的时候使用何种类型的锁
- 读操作希望读已经被其他事务排他锁住的数据时,怎么办?在这种情况下,SQL Server可以:
- 一直等到其他事务释放锁
- 读没有提交的数据
- 读数据最后提交后的版本
ANSI 99定义了4种事务隔离级别,SQL
Server 2005能够完全支持这些级别:
- 未提交读 在读数据时不会检查或使用任何锁。因此,在这种隔离级别中可能读取到没有提交的数据。
- 已提交读 只读取提交的数据并等待其他事务释放排他锁。读数据的共享锁在读操作完成后立即释放。已提交读是SQL Server的默认隔离级别。
- 可重复读 像已提交读级别那样读数据,但会保持共享锁直到事务结束。
- 可序列化 工作方式类似于可重复读。但它不仅会锁定受影响的数据,还会锁定这个范围。这就阻止了新数据插入查询所涉及的范围,这种情况可以导致幻像读。
此外,SQL Server还有两种使用行版本控制来读取数据的事务级别(本章后文将详细检验这些隔离级别)。行版本控制允许一个事务在数据排他锁定后读取数据的最后提交版本。由于不必等待到锁释放就可进行读操作,因此查询性能得以大大增强。这两种隔离级别如下:
- 已提交读快照 它是一种提交读级别的新实现。不像一般的提交读级别,SQL Server会读取最后提交的版本并因此不必在进行读操作时等待直到锁被释放。这个级别可以替代提交读级别。
- 快照 这种隔离使用行版本来提供事务级别的读取一致性。这意味着在一个事务中,由于读一致性可以通过行版本控制实现,因此同样的数据总是可以像在可序列化级别上一样被读取而不必为防止来自其他事务的更改而被锁定。
无论定义什么隔离级别,对数据的更改总是通过排他锁来锁定并直到事务结束时才释放。
很多情况下,定义正确的隔离级别并不是一个简单的决定。作为一种通用的规则,要选择在尽可能短的时间内锁住最少数据,但同时依然可以为事务提供它所需的安全程度的隔离级别。
已提交读
在SQL Server 2005中, 已提交读隔离级别是建立连接时的默认隔离级别。这个级别存在两种类型:已提交读和已提交读快照隔离级别。应用哪种类型由数据库选项定义。已提交读级别会在 读数据之前等待,直到阻塞锁被释放。已提交读快照级别会在数据被其他事务阻塞时使用行版本控制来读数据最后一次提交的版本。
使用已提交读级别:
TRAN
, EmailAddress
.ContactID 返回Gustavo
Achong。
现在假设另一事务在事务打开状态下更改了USEBEGINUPDATE
Contact
=WHERE
1
1中的事务读取。因为已提交读级别并不会在事务结束前保持用于<span courier="" new';"=""
lang="EN-US" style="padding: 0px; margin: 0px; font-size: 10pt; color: rgb(51, 51, 51); line-height: 19px; ">SELECT语句的共享锁。共享锁会在数据读取之后立即被1并尝试再次读数据:
FirstName, PersonWHERE
1
SQL Server会尝试在UPDATE语句对其有一个排他锁,因此这个操作不可能完成。虽然查询窗口),但排他锁依然存在。这个阻塞将持续存在,因为数据更改的排他锁会一直保持直到事务结束。
切换到查询窗口SELECT语句检查数据库中的授权和等待的锁。
可以看一个状态为2中的查询,后者在同样的资源上有一个排他锁。
在查询窗口 1。可以看到,查询窗口1中的查询不再被阻塞。由于查询窗口2中的事务被提交,则查询窗口COMMIT(默认1读取数据的话,可以使用这样的方法:
, EmailAddress
. )
= 使用NOLOCK 提示将启用"未提交读"行为。在<span
courier="" new';"="" lang="EN-US" style="padding: 0px; margin: 0px; font-size: 10pt; color: rgb(51, 51, 51); line-height: 19px; "> SQL Server Mobile 中,使用USE;
DATABASE ON
注意:设置 READ_COMMITTED_SNAPSHOT 选项时,数据库中仅允许存在执行 ALTER
DATABASE 命令的连接。在ALTER DATABASE 完成之前,数据库中不允许有其他打开的连接。数据库不必处于单用户模式。
现在,执行以下代码开始一个事务并像前面一样更改USEBEGINUPDATEContact
=WHERE 1 AdventureWorks FirstName, PersonWHERE 1 USE;
DATABASE OFF
重要提示 这个隔离级别可以用于减少阻塞。但要意识到这是一个数据库选项。当它发生了更改,将在数据库系统中使用已提交读级别的所有事务也会改变它们的行为。因此,只有在所有这些事务读最后提交的数据版本与读真正提交的数据版本在逻辑上同样正确的时候,使用这种级别才是明智的。
获取一致的可重复读操作
已提交读级别的一个缺点是,一个事务读取的数据在事务运行期间可能被另一个事务更改。因此,在两种已提交读级别下,不能保证一致性读。获取一致性读的意思是,在一个事务中,读取的数据始终是一样的。
1.USE;
DATABASE ON AdventureWorks ISOLATION LineTotalas SalesWHERE 43659
参数 除非正在恢复数据库,否则 SNAPSHOT 事务读取数据。
3.SNAPSHOT 事务会一直被阻塞。当事务取得授权之后,便会立即释放锁。
4. ON,才能开始一个使用ALLOW_SNAPSHOT_ISOLATION 都设置为 SNAPSHOT 隔离级别,否则将导致事务中止。如果一个事务在BEGIN
TRANSACTION 语句开始。
6. UPDATE,然后对同一个表发出 AdventureWorks SalesSET 5
= ProductID SELECT( FROMSalesOrderDetail
= LineTotalas SalesWHERE 43659
执行以下代码关闭AdventureWorks数据库的快照隔离级别:
DATABASE OFF AdventureWorks ISOLATION READ
TRAN
, ProductIDFROMSalesOrderDetail
= 对
事务中的每个语句所读取的全部数据都设置了共享锁,并且该共享锁一直保持到事务完成为止。这样可以防止其他事务修改当前事务读取的任何行。其他事务可以插 入与当前事务所发出语句的搜索条件相匹配的新行。如果当前事务随后重试执行该语句,它会检索新行,从而产生幻读。由于共享锁一直保持到事务结束,而不是在 每个语句结束时释放,所以并发级别低于默认的 READ COMMITTED 隔离级别。此选项只在必要时使用。
打开第二个查询窗口并执行以下代码尝试更新SalesOrderDetail表以更改查询窗口1中要使用的基础数据:
. OrderQty WHERE 43659
=<span courier="" new';"="" lang="EN-US" style="padding: 0px; margin: 0px;
background-image: none; background-attachment: scroll; background-color: white; font-size: 10pt; color: rgb(51, 51, 51); line-height: 19px; background-position: 0% 50%; background-repeat: repeat repeat; "> 777
查询会等待。不像快照隔离级别,不可能更新数据,因为共享锁会保持以防止其他事务更改数据。这个锁可以通过前面用过的管理视图 . SalesOrderID,
ProductID,
UnitPriceDiscount
('4911-403C-98',1,SELECT SalesOrderDetailID, SalesWHERE 43659
TRAN
可以观察到,新行被SELECT语句读取了,因为它处于这个语句的查询范围之内。可重复读级别会阻止现有数据被更改,但不会阻止新数据插入SELECT语句的查询范围内。
其他
TRANSACTION一共有以下几种级别:
TRANSACTION UNCOMMITTED
READ| [
; ]
上面的例子中没有提到的几种隔离级别的说明:
- READ UNCOMMITTED
指定语句可以读取已由其他事务修改但尚未提交的行。
在 READ UNCOMMITTED 级别运行的事务,不会发出共享锁来防止其他事务修改当前事务读取的数据。READ
UNCOMMITTED 事务也不会被排他锁阻塞,排他锁会禁止当前事务读取其他事务已修改但尚未提交的行。设置此选项之后,可以读取未提交的修改,这种读取称为脏读。在事务结束之前,可以更改数据中的值,行也可以出现在数据集中或从数据集中消失。该选项的作用与在事务内所有 SELECT 语句中的所有表上设置 NOLOCK 相同。这是隔离级别中限制最少的级别。
在 SQL Server 2005 中,您还可以使用下列任意一种方法,在保护事务不脏读未提交的数据修改的同时尽量减少锁定争用:
1. 如果将 READ_COMMITTED_SNAPSHOT 设置为 OFF(默认设置),则数据库引擎 会使用共享锁防止其他事务在当前事务执行读取操作期间修改行。共享锁还会阻止语句在其他事务完成之前读取由这些事务修改的行。语句完成后便会释放共享锁。
2. 任何其他事务都不能在当前事务完成之前修改由当前事务读取的数据。
3. 事务隔离级别定义了可为读取操作获取的锁类型。针对 READ COMMITTED 或 REPEATABLE
READ 获取的共享锁通常为行锁,尽管当读取引用了页或表中大量的行时,行锁可以升级为页锁或表锁。如果某行在被读取之后由事务进行了修改,则该事务会获取一个用于保护该行的排他锁,并且该排他锁在事务完成之前将一直保持。例如,如果 REPEATABLE READ 事务具有用于某行的共享锁,并且该事务随后修改了该行,则共享行锁便会转换为排他行锁。
3. 如果在存储过程或触发器中发出 SET TRANSACTION ISOLATION LEVEL,则当对象返回控制时,隔离级别会重设为在调用对象时有效的级别。例如,如果在批处理中设置 REPEATABLE
READ,并且该批处理调用一个将隔离级别设置为 SERIALIZABLE 的存储过程,则当该存储过程将控制返回给该批处理时,隔离级别就会恢复为REPEATABLE READ。