事务的隔离级别

事务的隔离级别SQL Server经过在锁资源上使用不一样类型的锁来隔离事务。为了开发安全的事务,定义事务内容以及应在何种状况下回滚相当重要,定义如何以及在多长时间内在事务中保持锁定也同等重要。这由隔离级别决定。应用不一样的隔离级别,SQL Server赋予开发者一种能力,让他们为每个单独事务定义与其余事务的隔离程度。事务隔离级别的定义以下:数据库

  • 是否在读数据的时候使用锁
  • 读锁持续多长时间
  • 在读数据的时候使用何种类型的锁
  • 读操做但愿读已经被其余事务排他锁住的数据时,怎么办?在这种状况下,SQL Server能够:
    • 一直等到其余事务释放锁
    • 读没有提交的数据
    • 读数据最后提交后的版本

ANSI 99定义了4种事务隔离级别,SQL Server 2005可以彻底支持这些级别:安全

  • 未提交读 在读数据时不会检查或使用任何锁。所以,在这种隔离级别中可能读取到没有提交的数据。
  • 已提交读 只读取提交的数据并等待其余事务释放排他锁。读数据的共享锁在读操做完成后当即释放。已提交读是SQL Server的默认隔离级别。
  • 可重复读 像已提交读级别那样读数据,但会保持共享锁直到事务结束。
  • 可序列化 工做方式相似于可重复读。但它不只会锁定受影响的数据,还会锁定这个范围。这就阻止了新数据插入查询所涉及的范围,这种状况能够致使幻像读。

此外,SQL Server还有两种使用行版本控制来读取数据的事务级别(本章后文将详细检验这些隔离级别)。行版本控制容许一个事务在数据排他锁定后读取数据的最后提交版本。因为没必要等待到锁释放就可进行读操做,所以查询性能得以大大加强。这两种隔离级别以下:并发

  • 已提交读快照 它是一种提交读级别的新实现。不像通常的提交读级别,SQL Server会读取最后提交的版本并所以没必要在进行读操做时等待直到锁被释放。这个级别能够替代提交读级别。
  • 快照 这种隔离使用行版原本提供事务级别的读取一致性。这意味着在一个事务中,因为读一致性能够经过行版本控制实现,所以一样的数据老是能够像在可序列化级别上同样被读取而没必要为防止来自其余事务的更改而被锁定。

不管定义什么隔离级别,对数据的更改老是经过排他锁来锁定并直到事务结束时才释放。ide

不少状况下,定义正确的隔离级别并非一个简单的决定。做为一种通用的规则,要选择在尽量短的时间内锁住最少数据,但同时依然能够为事务提供它所需的安全程度的隔离级别。工具

已提交读性能

在SQL Server 2005中,已提交读隔离级别是创建链接时的默认隔离级别。这个级别存在两种类型:已提交读和已提交读快照隔离级别。应用哪一种类型由数据库选项定义。已提交读级别会在读数据以前等待,直到阻塞锁被释放。已提交读快照级别会在数据被其余事务阻塞时使用行版本控制来读数据最后一次提交的版本。版本控制

使用已提交读级别:对象

BEGIN TRAN事务

SELECTci

    FirstName, LastName, EmailAddress

FROM

    Person.Contact

WHERE

ContactID = 1

返回EmailAddress为gustavo0@adventure-works.com的联系人Gustavo Achong。

如今假设另外一事务在事务打开状态下更改了EmailAddress。打开第二个查询窗口并执行如下批来UPDATE EmailAddress,但不提交事务:

USE AdventureWorks;

BEGIN TRAN

UPDATE

    Person.Contact

SET

    EmailAddress = 'uncommitted@email.at'

WHERE

    ContactID = 1

这个UPDATE 语句会正常运行。一行受到了影响,即便数据在这个事务尚未运行完以前已被查询窗口1中的事务读取。由于已提交读级别并不会在事务结束前保持用于SELECT语句的共享锁。共享锁会在数据读取以后当即被SQL Server释放。须要一致读的时候这将是一个问题。咱们将下面的"获取一致的可重复读操做"实现。

如今切换到查询窗口1并尝试再次读数据:

SELECT

        FirstName, LastName, EmailAddress

FROM

        Person.Contact

WHERE

        ContactID = 1

因为SELECT语句被阻塞,所以这个查询并无结束。SQL Server会尝试在ContactID= 1的键上获取一个共享锁,可是因为在查询窗口2中的UPDATE语句对其有一个排他锁,所以这个操做不可能完成。虽然查询窗口2处于已提交读级别(因为您没有更改默认级别),但排他锁依然存在。这个阻塞将持续存在,由于数据更改的排他锁会一直保持直到事务结束。

切换到查询窗口2,让查询窗口1中的查询继续运行。键入并执行如下SELECT语句检查数据库中的受权和等待的锁。

能够看一个状态为WAIT的共享锁。这是查询窗口1中运行的查询。它在等待查询窗口2中的查询,后者在一样的资源上有一个排他锁。

在查询窗口2中执行一个ROLLBACK TRAN语句来回滚UPDATE语句。而后切换回查询窗口1。能够看到,查询窗口1中的查询完成了,而且其结果与之前的同样。查询窗口2中的事务结束的时候,锁被释放了,以致查询窗口1中的查询再也不被阻塞。因为查询窗口2中的事务回滚,所以查询窗口1中获得的结果是原来的数据。若是查询窗口2中的事务被提交,则查询窗口1中会获得新的数据做为结果。

在查询窗口1中执行一个COMMIT TRAN语句并关闭全部的查询窗口。

能够看出,在(默认)已提交读级别中SQL Server会等到排他锁释放以后再进行读操做,以此来获取真正的提交数据。还能够看出,共享锁会持续到数据被读取以后,而排他锁会持续到事务提交以后。在许多事务几乎同时更改数据的时候这种行为可能会形成问题。在这些状况下,因为排他锁形成的阻塞,读数据会很是慢。但在有些状况下,使用最后提交的数据版本是恰当的。在这些状况下,能够将已提交读级别更改成已提交读快照级别。

若是要在窗口1读取数据的话,可使用这样的方法:

SELECT

    FirstName, LastName, EmailAddress

FROM

    Person.Contact WITH (NOLOCK)

WHERE

    ContactID = 1

让它取消全部的锁机制,那么排他锁也不会影响到这句查询。

使用NOLOCK注意:在 SQL Server 中,NOLOCK 提示将启用"未提交读"行为。在 SQL Server Mobile 中,使用 NOLOCK 提示仍会赋予"提交读"隔离级别。SQL Server Mobile 将维护数据副本,以确保能够读取数据而不须要使用共享锁帮助保护数据。

使用已提交读快照级别

激活已提交读快照级别

USE master;

ALTER DATABASE AdventureWorks

SET READ_COMMITTED_SNAPSHOT ON

注意:设置 READ_COMMITTED_SNAPSHOT 选项时,数据库中仅容许存在执行 ALTER DATABASE 命令的链接。在 ALTER DATABASE 完成以前,数据库中不容许有其余打开的链接。数据库没必要处于单用户模式。

如今,执行如下代码开始一个事务并像前面同样更改EmailAddress(但要让事务处于打开状态):

USE AdventureWorks;

BEGIN TRAN

UPDATE Person.Contact

SET EmailAddress = 'uncommitted@email.at'

WHERE ContactID = 1;

打开第二个查询窗口并执行如下语句来读取ContactID 1的列Name和EmailAddress列。

USE AdventureWorks;

BEGIN TRAN

SELECT FirstName, LastName, EmailAddress

FROM Person.Contact

WHERE ContactID = 1;

返回了联系人Gustavo Achong的EmailAddress gustavo0@adventure-works.com,这是这一行最后提交的版本。不像没有快照的已提交读级别那样,这个查询不会被阻塞。关闭查询窗口2并切换到查询窗口1。

执行如下语句来回滚事务并切换回已提交读级别(这个查询将等待直到关闭查询窗口2):

ROLLBACK TRAN

GO

USE master;

ALTER DATABASE AdventureWorks

SET READ_COMMITTED_SNAPSHOT OFF

重要提示 这个隔离级别能够用于减小阻塞。但要意识到这是一个数据库选项。当它发生了更改,将在数据库系统中使用已提交读级别的全部事务也会改变它们的行为。所以,只有在全部这些事务读最后提交的数据版本与读真正提交的数据版本在逻辑上一样正确的时候,使用这种级别才是明智的。

获取一致的可重复读操做

已提交读级别的一个缺点是,一个事务读取的数据在事务运行期间可能被另外一个事务更改。所以,在两种已提交读级别下,不能保证一致性读。获取一致性读的意思是,在一个事务中,读取的数据始终是同样的。

1. 已提交读在读数据的时候使用共享锁,但在读操做完成后会当即释放这个锁。所以,其余事务能够更改刚被读过的数据。

2. 已提交读快照读取最后一次提交的数据版本。当它第二次读数据的时候,最后一次提交的版本可能因为第二个事务已经提交了对数据的更改而变成一个新版本。

在须要一致性读的时候(例如对于报表),可能这种不一致性会致使问题。想象一下,您的事务经过数据计算了一些商业数值。在已提交读级别中进行这种计算的时候,可能因为基础数据在事务计算过程当中发生了变化而致使这些值被错误计算。为了成功地执行这个计算,可使用快照隔离级别。它会使用行版本管理来提供数据的提交版本,但与已提交读快照不一样的是,它总会提供在开始事务时最后提交的数据版本。所以,SQL Server始终会在整个事务执行过程当中获取一样的数据。

使用快照隔离级别

快照隔离级别须要在数据库中一次性地激活。激活以后,每一个链接能够在须要的时候使用它。

USE master;

ALTER DATABASE AdventureWorks

SET ALLOW_SNAPSHOT_ISOLATION ON;

如今假设咱们但愿运行一些基于Sales.SalesOrderDetail表的报表,但须要一致性的读操做。执行如下语句为事务激活快照隔离级别并开始一个返回订单行合计的事务。记住OrderTotal的值。

USE AdventureWorks;

SET TRANSACTION ISOLATION LEVEL SNAPSHOT

BEGIN TRAN

SELECT SUM(LineTotal) as OrderTotal

FROM Sales.SalesOrderDetail

WHERE SalesOrderID = 43659

参数SNAPSHOT的含义:

1. 指定事务中任何语句读取的数据都将是在事务开始时便存在的数据的事务上一致的版本。事务只能识别在其开始以前提交的数据修改。在当前事务中执行的语句将看不到在当前事务开始之后由其余事务所作的数据修改。其效果就好像事务中的语句得到了已提交数据的快照,由于该数据在事务开始时就存在。

2. 除非正在恢复数据库,不然 SNAPSHOT 事务不会在读取数据时请求锁。读取数据的 SNAPSHOT 事务不会阻止其余事务写入数据。写入数据的事务也不会阻止 SNAPSHOT 事务读取数据。

3. 在数据库恢复的回滚阶段,若是尝试读取由其余正在回滚的事务锁定的数据,则 SNAPSHOT 事务将请求一个锁。在事务完成回滚以前,SNAPSHOT 事务会一直被阻塞。当事务取得受权以后,便会当即释放锁。

4. 必须将 ALLOW_SNAPSHOT_ISOLATION 数据库选项设置为 ON,才能开始一个使用 SNAPSHOT 隔离级别的事务。若是使用 SNAPSHOT 隔离级别的事务访问多个数据库中的数据,则必须在每一个数据库中将 ALLOW_SNAPSHOT_ISOLATION 都设置为 ON。

5. 不能将经过其余隔离级别开始的事务设置为 SNAPSHOT 隔离级别,不然将致使事务停止。若是一个事务在 SNAPSHOT 隔离级别开始,则能够将它更改成另外一个隔离级别,而后再返回 SNAPSHOT。一个事务从执行 BEGIN TRANSACTION 语句开始。

6. 在 SNAPSHOT 隔离级别下运行的事务能够查看由该事务所作的更改。例如,若是事务对表执行 UPDATE,而后对同一个表发出 SELECT 语句,则修改后的数据将包含在结果集中。

打开第二个查询窗口并更新SalesOrderDetail表以更改查询窗口1中用到的基础数据。(若是但愿重复这个示例,将OrderQty的值5更改成其余数字以使如下代码能真正地更改数据库中的数据):

USE AdventureWorks;

UPDATE Sales.SalesOrderDetail

SET OrderQty = 5

WHERE SalesOrderID = 43659

AND ProductID = 777

关闭查询窗口2,切换到查询窗口1,而后重复下面的SELECT语句。

SELECT SUM(LineTotal) as OrderTotal

FROM Sales.SalesOrderDetail

WHERE SalesOrderID = 43659

能够看出,因为快照隔离级别忽略了事务运行过程当中数据的更改,所以结果与之前的相同。在快照级别下总会提供在事务开始时最后提交的值。

提交这个事务并执行如下代码再次重复这个查询:如今可看到,因为事务结束了,所以结果发生了变化。

COMMIT TRAN

SELECT SUM(LineTotal) as OrderTotal

FROM Sales.SalesOrderDetail

WHERE SalesOrderID = 43659

执行如下代码关闭AdventureWorks数据库的快照隔离级别:

ALTER DATABASE AdventureWorks

SET ALLOW_SNAPSHOT_ISOLATION OFF;

避免同时发生的数据更新

如前所述,快照隔离级别并不在读操做的时候锁定数据,但可以在整个事务中提供一致性的视图。在某些状况下,有必要在整个事务的执行过程当中锁定数据以免其余事务对数据的更改。假设但愿为一个订单×××。首先须要获取数据并检查它,而后为其生成发票。在这种状况下,须要从事务起始就锁定数据以免其余事务更改它。在这种状况下,快照隔离或者已提交读隔离级别都不是好的选择。对于这种状况,可使用可重复读隔离级别。这个隔离级别与没有快照的已提交读级别的工做过程类似,但它会保持共享锁直至事务结束。所以,它防止了对数据的更新。

使用可重复读隔离级别

假设但愿处理OrderID为43659的订单。首先,必须选择数据。为了防止其余事务更改正在读的数据,使用可重复读隔离。

USE AdventureWorks;

SET TRANSACTION ISOLATION LEVEL REPEATABLE READ

BEGIN TRAN

SELECT SalesOrderID, SalesOrderDetailID, ProductID, OrderQty

FROM Sales.SalesOrderDetail

WHERE SalesOrderID = 43659

参数REPEATABLE READ的含义:

1. 指定语句不能读取已由其余事务修改但还没有提交的行,而且指定,其余任何事务都不能在当前事务完成以前修改由当前事务读取的数据。

2. 对事务中的每一个语句所读取的所有数据都设置了共享锁,而且该共享锁一直保持到事务完成为止。这样能够防止其余事务修改当前事务读取的任何行。其余事务能够插入与当前事务所发出语句的搜索条件相匹配的新行。若是当前事务随后重试执行该语句,它会检索新行,从而产生幻读。因为共享锁一直保持到事务结束,而不是在每一个语句结束时释放,因此并发级别低于默认的 READ COMMITTED 隔离级别。此选项只在必要时使用。

打开第二个查询窗口并执行如下代码尝试更新SalesOrderDetail表以更改查询窗口1中要使用的基础数据:

UPDATE Sales.SalesOrderDetail

SET OrderQty = 5

WHERE SalesOrderID = 43659

AND ProductID = 777

查询会等待。不像快照隔离级别,不可能更新数据,由于共享锁会保持以防止其余事务更改数据。这个锁能够经过前面用过的管理视图sys.dm_tran_locks查看。

单击工具条上的"取消执行查询"按钮取消在查询窗口2中的查询。而执行如下INSERT语句在订单中加入一个新行项。

INSERT INTO Sales.SalesOrderDetail

(

    SalesOrderID,

    CarrierTrackingNumber,

    OrderQty,

    ProductID,

    SpecialOfferID,

    UnitPrice,

    UnitPriceDiscount

)

VALUES(43659,'4911-403C-98',1,758,1,874,0)

注意,即便正处于可重复读隔离级别,这个语句也会成功执行。由于可重复读会锁定数据以阻止数据的更新,但INSERT语句向数据库中插入新数据,这是容许的。新行处于查询窗口1中事务SELECT语句的查询范围之中,因此会在事务下一次获取相同数据的时候被读取到。这称做幻像读。

重复SELECT语句并提交这个事务,以下所示:

SELECT SalesOrderID, SalesOrderDetailID, ProductID, OrderQty

FROM Sales.SalesOrderDetail

WHERE SalesOrderID = 43659

COMMIT TRAN

能够观察到,新行被SELECT语句读取了,由于它处于这个语句的查询范围以内。可重复读级别会阻止现有数据被更改,但不会阻止新数据插入SELECT语句的查询范围内。

其余

SET TRANSACTION一共有如下几种级别:

SET TRANSACTION ISOLATION LEVEL

{ READ UNCOMMITTED

| READ COMMITTED

| REPEATABLE READ

| SNAPSHOT

| SERIALIZABLE

}

[ ; ]

上面的例子中没有提到的几种隔离级别的说明:

  1. READ UNCOMMITTED

指定语句能够读取已由其余事务修改但还没有提交的行。

在 READ UNCOMMITTED 级别运行的事务,不会发出共享锁来防止其余事务修改当前事务读取的数据。READ UNCOMMITTED 事务也不会被排他锁阻塞,排他锁会禁止当前事务读取其余事务已修改但还没有提交的行。设置此选项以后,能够读取未提交的修改,这种读取称为脏读。在事务结束以前,能够更改数据中的值,行也能够出如今数据集中或从数据集中消失。该选项的做用与在事务内全部 SELECT 语句中的全部表上设置 NOLOCK 相同。这是隔离级别中限制最少的级别。

在 SQL Server 2005 中,您还可使用下列任意一种方法,在保护事务不脏读未提交的数据修改的同时尽可能减小锁定争用:

1. READ COMMITTED 隔离级别,并将 READ_COMMITTED_SNAPSHOT 数据库选项设置为 ON。

2. SNAPSHOT 隔离级别。

  1. READ COMMITTED

指定语句不能读取已由其余事务修改但还没有提交的数据。这样能够避免脏读。其余事务能够在当前事务的各个语句之间更改数据,从而产生不可重复读取和幻像数据。该选项是 SQL Server 的默认设置。

READ COMMITTED 的行为取决于 READ_COMMITTED_SNAPSHOT 数据库选项的设置:

1. 若是将 READ_COMMITTED_SNAPSHOT 设置为 OFF(默认设置),则数据库引擎 会使用共享锁防止其余事务在当前事务执行读取操做期间修改行。共享锁还会阻止语句在其余事务完成以前读取由这些事务修改的行。语句完成后便会释放共享锁。

2. 若是将 READ_COMMITTED_SNAPSHOT 设置为 ON,则数据库引擎 会使用行版本控制为每一个语句提供一个在事务上一致的数据快照,由于该数据在语句开始时就存在。不使用锁来防止其余事务更新数据。

当 READ_COMMITTED_SNAPSHOT 数据库选项设置为 ON 时,您可使用 READCOMMITTEDLOCK 表提示为 READ_COMMITTED 隔离级别上运行的事务中的各语句请求共享锁,而不是行版本控制。

注意:设置 READ_COMMITTED_SNAPSHOT 选项时,数据库中仅容许存在执行 ALTER DATABASE 命令的链接。在 ALTER DATABASE 完成以前,数据库中不容许有其余打开的链接。数据库没必要处于单用户模式。

  1. SERIALIZABLE

请指定下列内容:

1

事务的隔离级别

SQL Server经过在锁资源上使用不一样类型的锁来隔离事务。为了开发安全的事务,定义事务内容以及应在何种状况下回滚相当重要,定义如何以及在多长时间内在事务中保持锁定也同等重要。这由隔离级别决定。应用不一样的隔离级别,SQL Server赋予开发者一种能力,让他们为每个单独事务定义与其余事务的隔离程度。事务隔离级别的定义以下:

  • 是否在读数据的时候使用锁
  • 读锁持续多长时间
  • 在读数据的时候使用何种类型的锁
  • 读操做但愿读已经被其余事务排他锁住的数据时,怎么办?在这种状况下,SQL Server能够:
    • 一直等到其余事务释放锁
    • 读没有提交的数据
    • 读数据最后提交后的版本

ANSI 99定义了4种事务隔离级别,SQL Server 2005可以彻底支持这些级别:

  • 未提交读 在读数据时不会检查或使用任何锁。所以,在这种隔离级别中可能读取到没有提交的数据。
  • 已提交读 只读取提交的数据并等待其余事务释放排他锁。读数据的共享锁在读操做完成后当即释放。已提交读是SQL Server的默认隔离级别。
  • 可重复读 像已提交读级别那样读数据,但会保持共享锁直到事务结束。
  • 可序列化 工做方式相似于可重复读。但它不只会锁定受影响的数据,还会锁定这个范围。这就阻止了新数据插入查询所涉及的范围,这种状况能够致使幻像读。

此外,SQL Server还有两种使用行版本控制来读取数据的事务级别(本章后文将详细检验这些隔离级别)。行版本控制容许一个事务在数据排他锁定后读取数据的最后提交版本。因为没必要等待到锁释放就可进行读操做,所以查询性能得以大大加强。这两种隔离级别以下:

  • 已提交读快照 它是一种提交读级别的新实现。不像通常的提交读级别,SQL Server会读取最后提交的版本并所以没必要在进行读操做时等待直到锁被释放。这个级别能够替代提交读级别。
  • 快照 这种隔离使用行版原本提供事务级别的读取一致性。这意味着在一个事务中,因为读一致性能够经过行版本控制实现,所以一样的数据老是能够像在可序列化级别上同样被读取而没必要为防止来自其余事务的更改而被锁定。

不管定义什么隔离级别,对数据的更改老是经过排他锁来锁定并直到事务结束时才释放。

不少状况下,定义正确的隔离级别并非一个简单的决定。做为一种通用的规则,要选择在尽量短的时间内锁住最少数据,但同时依然能够为事务提供它所需的安全程度的隔离级别。

已提交读

在SQL Server 2005中,已提交读隔离级别是创建链接时的默认隔离级别。这个级别存在两种类型:已提交读和已提交读快照隔离级别。应用哪一种类型由数据库选项定义。已提交读级别会在读数据以前等待,直到阻塞锁被释放。已提交读快照级别会在数据被其余事务阻塞时使用行版本控制来读数据最后一次提交的版本。

使用已提交读级别:

BEGIN TRAN

SELECT

    FirstName, LastName, EmailAddress

FROM

    Person.Contact

WHERE

ContactID = 1

返回EmailAddress为gustavo0@adventure-works.com的联系人Gustavo Achong。

如今假设另外一事务在事务打开状态下更改了EmailAddress。打开第二个查询窗口并执行如下批来UPDATE EmailAddress,但不提交事务:

USE AdventureWorks;

BEGIN TRAN

UPDATE

    Person.Contact

SET

    EmailAddress = 'uncommitted@email.at'

WHERE

    ContactID = 1

这个UPDATE 语句会正常运行。一行受到了影响,即便数据在这个事务尚未运行完以前已被查询窗口1中的事务读取。由于已提交读级别并不会在事务结束前保持用于SELECT语句的共享锁。共享锁会在数据读取以后当即被SQL Server释放。须要一致读的时候这将是一个问题。咱们将下面的"获取一致的可重复读操做"实现。

如今切换到查询窗口1并尝试再次读数据:

SELECT

        FirstName, LastName, EmailAddress

FROM

        Person.Contact

WHERE

        ContactID = 1

因为SELECT语句被阻塞,所以这个查询并无结束。SQL Server会尝试在ContactID= 1的键上获取一个共享锁,可是因为在查询窗口2中的UPDATE语句对其有一个排他锁,所以这个操做不可能完成。虽然查询窗口2处于已提交读级别(因为您没有更改默认级别),但排他锁依然存在。这个阻塞将持续存在,由于数据更改的排他锁会一直保持直到事务结束。

切换到查询窗口2,让查询窗口1中的查询继续运行。键入并执行如下SELECT语句检查数据库中的受权和等待的锁。

能够看一个状态为WAIT的共享锁。这是查询窗口1中运行的查询。它在等待查询窗口2中的查询,后者在一样的资源上有一个排他锁。

在查询窗口2中执行一个ROLLBACK TRAN语句来回滚UPDATE语句。而后切换回查询窗口1。能够看到,查询窗口1中的查询完成了,而且其结果与之前的同样。查询窗口2中的事务结束的时候,锁被释放了,以致查询窗口1中的查询再也不被阻塞。因为查询窗口2中的事务回滚,所以查询窗口1中获得的结果是原来的数据。若是查询窗口2中的事务被提交,则查询窗口1中会获得新的数据做为结果。

在查询窗口1中执行一个COMMIT TRAN语句并关闭全部的查询窗口。

能够看出,在(默认)已提交读级别中SQL Server会等到排他锁释放以后再进行读操做,以此来获取真正的提交数据。还能够看出,共享锁会持续到数据被读取以后,而排他锁会持续到事务提交以后。在许多事务几乎同时更改数据的时候这种行为可能会形成问题。在这些状况下,因为排他锁形成的阻塞,读数据会很是慢。但在有些状况下,使用最后提交的数据版本是恰当的。在这些状况下,能够将已提交读级别更改成已提交读快照级别。

若是要在窗口1读取数据的话,可使用这样的方法:

SELECT

    FirstName, LastName, EmailAddress

FROM

    Person.Contact WITH (NOLOCK)

WHERE

    ContactID = 1

让它取消全部的锁机制,那么排他锁也不会影响到这句查询。

使用NOLOCK注意:在 SQL Server 中,NOLOCK 提示将启用"未提交读"行为。在 SQL Server Mobile 中,使用 NOLOCK 提示仍会赋予"提交读"隔离级别。SQL Server Mobile 将维护数据副本,以确保能够读取数据而不须要使用共享锁帮助保护数据。

使用已提交读快照级别

激活已提交读快照级别

USE master;

ALTER DATABASE AdventureWorks

SET READ_COMMITTED_SNAPSHOT ON

注意:设置 READ_COMMITTED_SNAPSHOT 选项时,数据库中仅容许存在执行 ALTER DATABASE 命令的链接。在 ALTER DATABASE 完成以前,数据库中不容许有其余打开的链接。数据库没必要处于单用户模式。

如今,执行如下代码开始一个事务并像前面同样更改EmailAddress(但要让事务处于打开状态):

USE AdventureWorks;

BEGIN TRAN

UPDATE Person.Contact

SET EmailAddress = 'uncommitted@email.at'

WHERE ContactID = 1;

打开第二个查询窗口并执行如下语句来读取ContactID 1的列Name和EmailAddress列。

USE AdventureWorks;

BEGIN TRAN

SELECT FirstName, LastName, EmailAddress

FROM Person.Contact

WHERE ContactID = 1;

返回了联系人Gustavo Achong的EmailAddress gustavo0@adventure-works.com,这是这一行最后提交的版本。不像没有快照的已提交读级别那样,这个查询不会被阻塞。关闭查询窗口2并切换到查询窗口1。

执行如下语句来回滚事务并切换回已提交读级别(这个查询将等待直到关闭查询窗口2):

ROLLBACK TRAN

GO

USE master;

ALTER DATABASE AdventureWorks

SET READ_COMMITTED_SNAPSHOT OFF

重要提示 这个隔离级别能够用于减小阻塞。但要意识到这是一个数据库选项。当它发生了更改,将在数据库系统中使用已提交读级别的全部事务也会改变它们的行为。所以,只有在全部这些事务读最后提交的数据版本与读真正提交的数据版本在逻辑上一样正确的时候,使用这种级别才是明智的。

获取一致的可重复读操做

已提交读级别的一个缺点是,一个事务读取的数据在事务运行期间可能被另外一个事务更改。所以,在两种已提交读级别下,不能保证一致性读。获取一致性读的意思是,在一个事务中,读取的数据始终是同样的。

1. 已提交读在读数据的时候使用共享锁,但在读操做完成后会当即释放这个锁。所以,其余事务能够更改刚被读过的数据。

2. 已提交读快照读取最后一次提交的数据版本。当它第二次读数据的时候,最后一次提交的版本可能因为第二个事务已经提交了对数据的更改而变成一个新版本。

在须要一致性读的时候(例如对于报表),可能这种不一致性会致使问题。想象一下,您的事务经过数据计算了一些商业数值。在已提交读级别中进行这种计算的时候,可能因为基础数据在事务计算过程当中发生了变化而致使这些值被错误计算。为了成功地执行这个计算,可使用快照隔离级别。它会使用行版本管理来提供数据的提交版本,但与已提交读快照不一样的是,它总会提供在开始事务时最后提交的数据版本。所以,SQL Server始终会在整个事务执行过程当中获取一样的数据。

使用快照隔离级别

快照隔离级别须要在数据库中一次性地激活。激活以后,每一个链接能够在须要的时候使用它。

USE master;

ALTER DATABASE AdventureWorks

SET ALLOW_SNAPSHOT_ISOLATION ON;

如今假设咱们但愿运行一些基于Sales.SalesOrderDetail表的报表,但须要一致性的读操做。执行如下语句为事务激活快照隔离级别并开始一个返回订单行合计的事务。记住OrderTotal的值。

USE AdventureWorks;

SET TRANSACTION ISOLATION LEVEL SNAPSHOT

BEGIN TRAN

SELECT SUM(LineTotal) as OrderTotal

FROM Sales.SalesOrderDetail

WHERE SalesOrderID = 43659

参数SNAPSHOT的含义:

1. 指定事务中任何语句读取的数据都将是在事务开始时便存在的数据的事务上一致的版本。事务只能识别在其开始以前提交的数据修改。在当前事务中执行的语句将看不到在当前事务开始之后由其余事务所作的数据修改。其效果就好像事务中的语句得到了已提交数据的快照,由于该数据在事务开始时就存在。

2. 除非正在恢复数据库,不然 SNAPSHOT 事务不会在读取数据时请求锁。读取数据的 SNAPSHOT 事务不会阻止其余事务写入数据。写入数据的事务也不会阻止 SNAPSHOT 事务读取数据。

3. 在数据库恢复的回滚阶段,若是尝试读取由其余正在回滚的事务锁定的数据,则 SNAPSHOT 事务将请求一个锁。在事务完成回滚以前,SNAPSHOT 事务会一直被阻塞。当事务取得受权以后,便会当即释放锁。

4. 必须将 ALLOW_SNAPSHOT_ISOLATION 数据库选项设置为 ON,才能开始一个使用 SNAPSHOT 隔离级别的事务。若是使用 SNAPSHOT 隔离级别的事务访问多个数据库中的数据,则必须在每一个数据库中将 ALLOW_SNAPSHOT_ISOLATION 都设置为 ON。

5. 不能将经过其余隔离级别开始的事务设置为 SNAPSHOT 隔离级别,不然将致使事务停止。若是一个事务在 SNAPSHOT 隔离级别开始,则能够将它更改成另外一个隔离级别,而后再返回 SNAPSHOT。一个事务从执行 BEGIN TRANSACTION 语句开始。

6. 在 SNAPSHOT 隔离级别下运行的事务能够查看由该事务所作的更改。例如,若是事务对表执行 UPDATE,而后对同一个表发出 SELECT 语句,则修改后的数据将包含在结果集中。

打开第二个查询窗口并更新SalesOrderDetail表以更改查询窗口1中用到的基础数据。(若是但愿重复这个示例,将OrderQty的值5更改成其余数字以使如下代码能真正地更改数据库中的数据):

USE AdventureWorks;

UPDATE Sales.SalesOrderDetail

SET OrderQty = 5

WHERE SalesOrderID = 43659

AND ProductID = 777

关闭查询窗口2,切换到查询窗口1,而后重复下面的SELECT语句。

SELECT SUM(LineTotal) as OrderTotal

FROM Sales.SalesOrderDetail

WHERE SalesOrderID = 43659

能够看出,因为快照隔离级别忽略了事务运行过程当中数据的更改,所以结果与之前的相同。在快照级别下总会提供在事务开始时最后提交的值。

提交这个事务并执行如下代码再次重复这个查询:如今可看到,因为事务结束了,所以结果发生了变化。

COMMIT TRAN

SELECT SUM(LineTotal) as OrderTotal

FROM Sales.SalesOrderDetail

WHERE SalesOrderID = 43659

执行如下代码关闭AdventureWorks数据库的快照隔离级别:

ALTER DATABASE AdventureWorks

SET ALLOW_SNAPSHOT_ISOLATION OFF;

避免同时发生的数据更新

如前所述,快照隔离级别并不在读操做的时候锁定数据,但可以在整个事务中提供一致性的视图。在某些状况下,有必要在整个事务的执行过程当中锁定数据以免其余事务对数据的更改。假设但愿为一个订单×××。首先须要获取数据并检查它,而后为其生成发票。在这种状况下,须要从事务起始就锁定数据以免其余事务更改它。在这种状况下,快照隔离或者已提交读隔离级别都不是好的选择。对于这种状况,可使用可重复读隔离级别。这个隔离级别与没有快照的已提交读级别的工做过程类似,但它会保持共享锁直至事务结束。所以,它防止了对数据的更新。

使用可重复读隔离级别

假设但愿处理OrderID为43659的订单。首先,必须选择数据。为了防止其余事务更改正在读的数据,使用可重复读隔离。

USE AdventureWorks;

SET TRANSACTION ISOLATION LEVEL REPEATABLE READ

BEGIN TRAN

SELECT SalesOrderID, SalesOrderDetailID, ProductID, OrderQty

FROM Sales.SalesOrderDetail

WHERE SalesOrderID = 43659

参数REPEATABLE READ的含义:

1. 指定语句不能读取已由其余事务修改但还没有提交的行,而且指定,其余任何事务都不能在当前事务完成以前修改由当前事务读取的数据。

2. 对事务中的每一个语句所读取的所有数据都设置了共享锁,而且该共享锁一直保持到事务完成为止。这样能够防止其余事务修改当前事务读取的任何行。其余事务能够插入与当前事务所发出语句的搜索条件相匹配的新行。若是当前事务随后重试执行该语句,它会检索新行,从而产生幻读。因为共享锁一直保持到事务结束,而不是在每一个语句结束时释放,因此并发级别低于默认的 READ COMMITTED 隔离级别。此选项只在必要时使用。

打开第二个查询窗口并执行如下代码尝试更新SalesOrderDetail表以更改查询窗口1中要使用的基础数据:

UPDATE Sales.SalesOrderDetail

SET OrderQty = 5

WHERE SalesOrderID = 43659

AND ProductID = 777

查询会等待。不像快照隔离级别,不可能更新数据,由于共享锁会保持以防止其余事务更改数据。这个锁能够经过前面用过的管理视图sys.dm_tran_locks查看。

单击工具条上的"取消执行查询"按钮取消在查询窗口2中的查询。而执行如下INSERT语句在订单中加入一个新行项。

INSERT INTO Sales.SalesOrderDetail

(

    SalesOrderID,

    CarrierTrackingNumber,

    OrderQty,

    ProductID,

    SpecialOfferID,

    UnitPrice,

    UnitPriceDiscount

)

VALUES(43659,'4911-403C-98',1,758,1,874,0)

注意,即便正处于可重复读隔离级别,这个语句也会成功执行。由于可重复读会锁定数据以阻止数据的更新,但INSERT语句向数据库中插入新数据,这是容许的。新行处于查询窗口1中事务SELECT语句的查询范围之中,因此会在事务下一次获取相同数据的时候被读取到。这称做幻像读。

重复SELECT语句并提交这个事务,以下所示:

SELECT SalesOrderID, SalesOrderDetailID, ProductID, OrderQty

FROM Sales.SalesOrderDetail

WHERE SalesOrderID = 43659

COMMIT TRAN

能够观察到,新行被SELECT语句读取了,由于它处于这个语句的查询范围以内。可重复读级别会阻止现有数据被更改,但不会阻止新数据插入SELECT语句的查询范围内。

其余

SET TRANSACTION一共有如下几种级别:

SET TRANSACTION ISOLATION LEVEL

{ READ UNCOMMITTED

| READ COMMITTED

| REPEATABLE READ

| SNAPSHOT

| SERIALIZABLE

}

[ ; ]

上面的例子中没有提到的几种隔离级别的说明:

  1. READ UNCOMMITTED

指定语句能够读取已由其余事务修改但还没有提交的行。

在 READ UNCOMMITTED 级别运行的事务,不会发出共享锁来防止其余事务修改当前事务读取的数据。READ UNCOMMITTED 事务也不会被排他锁阻塞,排他锁会禁止当前事务读取其余事务已修改但还没有提交的行。设置此选项以后,能够读取未提交的修改,这种读取称为脏读。在事务结束以前,能够更改数据中的值,行也能够出如今数据集中或从数据集中消失。该选项的做用与在事务内全部 SELECT 语句中的全部表上设置 NOLOCK 相同。这是隔离级别中限制最少的级别。

在 SQL Server 2005 中,您还可使用下列任意一种方法,在保护事务不脏读未提交的数据修改的同时尽可能减小锁定争用:

1. READ COMMITTED 隔离级别,并将 READ_COMMITTED_SNAPSHOT 数据库选项设置为 ON。

2. SNAPSHOT 隔离级别。

  1. READ COMMITTED

指定语句不能读取已由其余事务修改但还没有提交的数据。这样能够避免脏读。其余事务能够在当前事务的各个语句之间更改数据,从而产生不可重复读取和幻像数据。该选项是 SQL Server 的默认设置。

READ COMMITTED 的行为取决于 READ_COMMITTED_SNAPSHOT 数据库选项的设置:

1. 若是将 READ_COMMITTED_SNAPSHOT 设置为 OFF(默认设置),则数据库引擎 会使用共享锁防止其余事务在当前事务执行读取操做期间修改行。共享锁还会阻止语句在其余事务完成以前读取由这些事务修改的行。语句完成后便会释放共享锁。

2. 若是将 READ_COMMITTED_SNAPSHOT 设置为 ON,则数据库引擎 会使用行版本控制为每一个语句提供一个在事务上一致的数据快照,由于该数据在语句开始时就存在。不使用锁来防止其余事务更新数据。

当 READ_COMMITTED_SNAPSHOT 数据库选项设置为 ON 时,您可使用 READCOMMITTEDLOCK 表提示为 READ_COMMITTED 隔离级别上运行的事务中的各语句请求共享锁,而不是行版本控制。

注意:设置 READ_COMMITTED_SNAPSHOT 选项时,数据库中仅容许存在执行 ALTER DATABASE 命令的链接。在 ALTER DATABASE 完成以前,数据库中不容许有其余打开的链接。数据库没必要处于单用户模式。

  1. SERIALIZABLE

请指定下列内容:

1. 语句不能读取已由其余事务修改但还没有提交的数据。

2. 任何其余事务都不能在当前事务完成以前修改由当前事务读取的数据。

3. 在当前事务完成以前,其余事务不能使用当前事务中任何语句读取的键值插入新行。

范围锁处于与事务中执行的每一个语句的搜索条件相匹配的键值范围以内。这样能够阻止其余事务更新或插入任何行,从而限定当前事务所执行的任何语句。这意味着若是再次执行事务中的任何语句,则这些语句便会读取同一组行。在事务完成以前将一直保持范围锁。这是限制最多的隔离级别,由于它锁定了键的整个范围,并在事务完成以前一直保持范围锁。由于并发级别较低,因此应只在必要时才使用该选项。该选项的做用与在事务内全部 SELECT 语句中的全部表上设置 HOLDLOCK 相同。

须要注意的地方:

1. 一次只能设置一个隔离级别选项,并且设置的选项将一直对那个链接始终有效,直到显式更改该选项为止。事务中执行的全部读取操做都会在指定的隔离级别的规则下运行,除非语句的 FROM 子句中的表提示为表指定了其余锁定行为或版本控制行为。

2. 事务隔离级别定义了可为读取操做获取的锁类型。针对 READ COMMITTED 或 REPEATABLE READ 获取的共享锁一般为行锁,尽管当读取引用了页或表中大量的行时,行锁能够升级为页锁或表锁。若是某行在被读取以后由事务进行了修改,则该事务会获取一个用于保护该行的排他锁,而且该排他锁在事务完成以前将一直保持。例如,若是 REPEATABLE READ 事务具备用于某行的共享锁,而且该事务随后修改了该行,则共享行锁便会转换为排他行锁。

3. 在事务进行期间,能够随时将事务从一个隔离级别切换到另外一个隔离级别,但有一种状况例外。即在从任一隔离级别更改到 SNAPSHOT 隔离时,不能进行上述操做。不然会致使事务失败并回滚。可是,能够将在 SNAPSHOT 隔离中启动的事务更改成任何其余隔离级别。

4. 将事务从一个隔离级别更改成另外一个隔离级别以后,便会根据新级别的规则对更改后读取的资源执行保护。在更改前读取的资源将继续按照之前级别的规则受到保护。例如,若是某个事务从 READ COMMITTED 更改成 SERIALIZABLE,则在该事务结束前,更改后所获取的共享锁将一直处于保留状态。

5. 若是在存储过程或触发器中发出 SET TRANSACTION ISOLATION LEVEL,则当对象返回控制时,隔离级别会重设为在调用对象时有效的级别。例如,若是在批处理中设置 REPEATABLE READ,而且该批处理调用一个将隔离级别设置为 SERIALIZABLE 的存储过程,则当该存储过程将控制返回给该批处理时,隔离级别就会恢复为 REPEATABLE READ。

. 语句不能读取已由其余事务修改但还没有提交的数据。

2. 任何其余事务都不能在当前事务完成以前修改由当前事务读取的数据。

3. 在当前事务完成以前,其余事务不能使用当前事务中任何语句读取的键值插入新行。

范围锁处于与事务中执行的每一个语句的搜索条件相匹配的键值范围以内。这样能够阻止其余事务更新或插入任何行,从而限定当前事务所执行的任何语句。这意味着若是再次执行事务中的任何语句,则这些语句便会读取同一组行。在事务完成以前将一直保持范围锁。这是限制最多的隔离级别,由于它锁定了键的整个范围,并在事务完成以前一直保持范围锁。由于并发级别较低,因此应只在必要时才使用该选项。该选项的做用与在事务内全部 SELECT 语句中的全部表上设置 HOLDLOCK 相同。

须要注意的地方:

1. 一次只能设置一个隔离级别选项,并且设置的选项将一直对那个链接始终有效,直到显式更改该选项为止。事务中执行的全部读取操做都会在指定的隔离级别的规则下运行,除非语句的 FROM 子句中的表提示为表指定了其余锁定行为或版本控制行为。

2. 事务隔离级别定义了可为读取操做获取的锁类型。针对 READ COMMITTED 或 REPEATABLE READ 获取的共享锁一般为行锁,尽管当读取引用了页或表中大量的行时,行锁能够升级为页锁或表锁。若是某行在被读取以后由事务进行了修改,则该事务会获取一个用于保护该行的排他锁,而且该排他锁在事务完成以前将一直保持。例如,若是 REPEATABLE READ 事务具备用于某行的共享锁,而且该事务随后修改了该行,则共享行锁便会转换为排他行锁。

3. 在事务进行期间,能够随时将事务从一个隔离级别切换到另外一个隔离级别,但有一种状况例外。即在从任一隔离级别更改到 SNAPSHOT 隔离时,不能进行上述操做。不然会致使事务失败并回滚。可是,能够将在 SNAPSHOT 隔离中启动的事务更改成任何其余隔离级别。

4. 将事务从一个隔离级别更改成另外一个隔离级别以后,便会根据新级别的规则对更改后读取的资源执行保护。在更改前读取的资源将继续按照之前级别的规则受到保护。例如,若是某个事务从 READ COMMITTED 更改成 SERIALIZABLE,则在该事务结束前,更改后所获取的共享锁将一直处于保留状态。

5. 若是在存储过程或触发器中发出 SET TRANSACTION ISOLATION LEVEL,则当对象返回控制时,隔离级别会重设为在调用对象时有效的级别。例如,若是在批处理中设置 REPEATABLE READ,而且该批处理调用一个将隔离级别设置为 SERIALIZABLE 的存储过程,则当该存储过程将控制返回给该批处理时,隔离级别就会恢复为 REPEATABLE READ。

相关文章
相关标签/搜索