四巨头第十四周作业翻译

事务隔离级别

韦恩·谢菲尔德,2014/02/13

我最近在SQL Server Central上发布了文章锁定、阻塞和死锁。本文将继续讨论事务隔离级别,以及事务隔离级别的选择如何影响先前文章中讨论的锁定机制。

如果我们查看数据库引擎中的Books Online (BOL)主题隔离级别,我们可以看到事务隔离级别控件:

l  是否在读取数据时获取锁,以及请求什么类型的锁。

l  读取锁的保存时间。

l  引用另一个事务修改的行的读取操作:

  • 直到行上的独占锁被释放为止。
  • 检索语句或事务启动时存在的行提交版本。
  • 读取未提交的数据修改。

注意,这些都只影响数据的读取。写入数据时获得的锁不受影响——仍然需要这些锁来保护数据修改。事务隔离级别控制如何保护读操作不受其他(写)操作的影响。

ISO隔离级别

下表显示了不同的ISO隔离级别,以及它们的并发性副作用:

隔离级别

脏读吗

不可重复读吗

幻读吗

失踪/双读吗

读未提交

Yes

Yes

Yes

Yes

读已提交

No

Yes

Yes

Yes

可重复读

No

No

Yes

No

可序列化的

No

No

No

No

当我们检查这个表时,我们可以看到不同的事务隔离级别被设计为消除并发性影响。

SQL Server 2005增加了两个额外的事务隔离级别,这两个级别都处理使用快照的问题:

隔离级别

脏读吗

不可重复读吗

幻读吗

失踪/双读吗

读取提交快照

No

No

No

No

快照

No

No

No

No

READ_COMMITTED_SNAPSHOT是数据库级别的设置,如果打开它并提交事务隔离级别,那么它将使用行版本控制在语句开始时显示数据的事务一致视图。

快照隔离级别还利用行版本控制在语句开始时显示数据的事务一致视图。这要求打开ALLOW_SNAPSHOT_ISOLATION数据库设置,并让查询发出SET事务隔离级别快照语句。

在这两个快照隔离级别中,结果是读者不会阻止作者,作者不会阻止读者。此外,读者将不能阅读任何飞行数据修改从其他事务。

正如我已经提到的,这两种方法都使用行版本控制。在使用行版本控制时,SQL Server中的数据库引擎将维护受事务影响的行的版本。利用row-versioning将:

1.消除读取事务上的共享锁。

2.减少阻塞(读取事务)。

3.增加数据修改所需的资源。

4.增加tempdb中的活动(其中存储行版本控制信息)。

a.所有数据库数据修改都有行版本控制。

5.每个数据记录都有一个14字节的记录后缀。

并发性的影响

上面的图表提到了几种不同的并发副作用,让我们来解释一下它们。这些效应在BOL中定义为并发效应:

脏读:当第二个事务选择由另一个事务更新的行时,会发生脏读(在ISO中称为“未提交依赖项”)。当修改后的数据在另一个事务提交正在修改数据的事务之前被读取时,会发生脏读。如果要回滚该事务,则第二个事务刚刚返回了包含数据库中不存在的数据的行。通过阻止正在更改的数据的读取,可以避免这种影响。

不可重复读当事务多次读取同一行时,就会发生不可重复读取(在ISO中称为“不一致分析”),并且不同的读取之间的结果是不同的。当另一个事务修改并将更改提交给行时,就会发生这种情况。与脏读类似,不同之处在于,在不可重复读中,写事务已经成功提交了事务,而在脏读中,写事务被回滚。可以通过防止数据更改,直到完成数据读取,从而避免这种影响。

幻读:当读取数据的事务正在读取数据范围,而另一个事务插入或删除一行时,就会发生虚读。如果再次发出读取事务发出的语句,将返回额外的行(对于insert事务),或者返回更少的行(对于delete事务)。通过防止在读取数据时插入或删除数据,可以避免这种影响。

缺失/双读发生在:

  • 读取事务在索引扫描操作中读取一系列行,在读取行期间由第二个事务更新,从而更改索引键列,从而更改其在扫描中的位置。如果更新将一行从扫描结束移动到开始,则读取事务可能会错过该行;相反,如果更新将行从扫描的开始移动到末尾,那么该行可以被读取两次。
  • 如果读取未提交隔离级别的读取事务执行分配顺序扫描(使用IAM页面)和另一个事务导致页拆分,则可以忽略读取事务读取的行。

当您阅读这些效果时,您应该能够看到,当您在努力防止任何这些并发效果时,您正在数据库中创建更多的锁定(从而可能产生更多的阻塞)。

并发效果的示例

让我们运行一些示例,看看这些不同的并发效果如何在不同的事务隔离级别中表现出来。所有这些示例都使用两个查询窗口;一个运行读事务,另一个运行写事务。查询使用“WAITFOR DELAY”给您一点时间来启动一个事务,然后切换到另一个查询窗口来运行另一个事务。

首先是数据库初始化代码。在运行每个测试之前,需要运行此代码。它被放入一个存储过程中,以便在必要时可以轻松运行。

IF DB_ID('IsolationLevelTest') IS NOT NULL BEGIN

    USE IsolationLevelTest;

    ALTER DATABASE IsolationLevelTest SET SINGLE_USER WITH ROLLBACK IMMEDIATE;

    USE master;

    DROP DATABASE IsolationLevelTest;

END;

CREATE DATABASE IsolationLevelTest;

GO

USE IsolationLevelTest;

GO

 

CREATE PROCEDURE dbo.db_reset AS

IF OBJECT_ID('dbo.IsolationTests','U') IS NOT NULL DROP TABLE dbo.IsolationTests;

CREATE TABLE dbo.IsolationTests (   

    Id      INTEGER IDENTITY,   

    ColA    CHAR(1));

INSERT INTO dbo.IsolationTests(ColA)

SELECT 'A' UNION ALL

SELECT 'A' UNION ALL

SELECT 'A' UNION ALL

SELECT 'A' UNION ALL

SELECT 'A' UNION ALL

SELECT 'A' UNION ALL

SELECT 'A';

 

SELECT  *

FROM    dbo.IsolationTests;

 

IF EXISTS (SELECT 1 FROM sys.databases WHERE database_id = DB_ID('IsolationLevelTest') AND snapshot_isolation_state = 1)

    ALTER DATABASE IsolationLevelTest SET ALLOW_SNAPSHOT_ISOLATION OFF;

GO

EXECUTE dbo.db_reset;

GO Read提交

在Read提交的测试中,我们将重新运行这些语句。设置第二个查询窗口以使用已提交的事务隔离级别。因此,在第二个查询窗口中运行的SELECT语句必须等到第一个事务完成(事务被提交或回滚)之后才能读取数据——它被打开的事务阻塞。

1在第一个查询窗口中,运行以下语句:

USE IsolationLevelTest;
GO
EXECUTE dbo.db_reset;
GO
 
BEGIN TRANSACTION;
UPDATE  dbo.IsolationTests
SET     ColA = 'Y';
--Simulate having some intensive processing here with a wait
WAITFOR DELAY '00:00:10';
ROLLBACK;

2在第二个查询窗口中,运行以下语句:

USE IsolationLevelTest;
GO
-- READ COMMITTED
-- Run this in query window 2 while the 1st query is running
SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
SELECT * FROM dbo.IsolationTests;

3如您所见,查询窗口2中的语句必须等待查询窗口1中的事务完成后才能运行,查询窗口2在查询窗口1完成后返回表中的值。

可重复读

对于下一个隔离级别,即可重复读取,我们将展示如何需要在此隔离级别中的一个事务,即两次从表读取数据,在读取之间有一段时间,才能返回相同的数据。在这个隔离级别下,它必须读取与所读取的行完全相同的数据,因此它将阻塞试图更新其中一些行的第二个事务。然后,我们将从可重复读取更改为Readed提交,以显示允许运行更新的效果。

1在第一个查询窗口中,运行以下语句:

USE IsolationLevelTest;
GO
EXECUTE dbo.db_reset;
GO
 
SET TRANSACTION ISOLATION LEVEL REPEATABLE READ;
BEGIN TRANSACTION;
SELECT * FROM dbo.IsolationTests;
WAITFOR DELAY '00:00:10';
SELECT * FROM IsolationTests;
ROLLBACK;

2在第二个查询窗口中,运行以下语句:

USE IsolationLevelTest;
GO
UPDATE dbo.IsolationTests SET Col1 = -1;

3请注意,查询窗口2等待查询窗口1完成,因为查询窗口1处于可重复读取状态。

4重新运行步骤1-3:

a更改查询窗口1以使用读取提交的隔离级别,并运行代码,

b在查询窗口2中运行代码。

5注意,查询窗口2立即完成,在查询窗口1中,第二个SELECT语句返回与第一个SELECT语句不同的结果。

可串行化的

在刚才执行的可重复读取测试中,我们看到了如何防止对数据的更新。可序列化的隔离级别进一步提高了这一步,还防止了对此表进行插入或删除。为了测试这一点,我们将从可重复读取中重新运行测试,并将隔离级别更改为可序列化,并尝试执行INSERT而不是更新。然后,我们将在可重复读取隔离级别上运行此测试,展示如何允许INSERT运行。

1在第一个查询窗口中,运行以下语句:

USE IsolationLevelTest;
GO
EXECUTE dbo.db_reset;
GO
 
-- SERIALIZABLE
-- Run this in query window 1
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
--SET TRANSACTION ISOLATION LEVEL REPEATABLE READ;  -- what will happen if this is set instead?
BEGIN TRANSACTION;
SELECT * FROM dbo.IsolationTests;
WAITFOR DELAY '00:00:10';
SELECT * FROM dbo.IsolationTests;
ROLLBACK;

2在第二个查询窗口中,运行以下语句:

USE IsolationLevelTest;
GO
INSERT INTO dbo.IsolationTests(ColA)
VALUES ('W');

3注意,查询窗口2中的插入等待到查询窗口1中的事务完成。

4重新运行步骤1-3:

a更改查询窗口1以使用可重复读取隔离级别,并运行代码,

b在查询窗口2中运行代码。

5注意,查询窗口2中的INSERT立即运行,查询窗口1中的第二个SELECT语句返回插入的行。

简介

我们已经看到的读取提交/未提交事务级别也存在丢失/双读的问题。可重复读取/可序列化隔离级别消除了此问题,但在严重阻塞其他事务的情况下进行此操作。快照隔离级别消除了与可序列化隔离级别相同的并发副作用,此外,在不引入锁定(从而消除阻塞)的情况下也会这样做。在这个测试中,我们将首先显示快照隔离级别中没有阻塞,然后显示在使用可序列化隔离级别时这些相同的语句是如何被阻塞的。

1在第一个查询窗口中,运行以下语句:

USE IsolationLevelTest;
GO
EXECUTE dbo.db_reset;
GO
 
-- SNAPSHOT
ALTER DATABASE IsolationLevelTest SET ALLOW_SNAPSHOT_ISOLATION ON;
GO
-- Run this in query window 1
USE IsolationLevelTest;
GO
SET TRANSACTION ISOLATION LEVEL SNAPSHOT;
BEGIN TRANSACTION;
SELECT * FROM dbo.IsolationTests;
WAITFOR DELAY '00:00:10';
SELECT * FROM dbo.IsolationTests;
ROLLBACK;

2在第二个查询窗口中,运行以下语句:

USE IsolationLevelTest;
GO
INSERT INTO dbo.IsolationTests(ColA)
VALUES ('X');
SELECT * FROM dbo.IsolationTests;

3请注意,查询窗口2立即完成,但数据修改没有反映在查询窗口1中。

4如果要更改查询窗口1以利用可序列化的隔离级别并重新运行测试,则会看到查询窗口2现在将被阻塞,并等待查询窗口1完成后才能插入行。

NOLOCK查询提示如何适合它?

表提示NOLOCK(与表提示READUNCOMMITTED相同)与指定SET事务级别的READUNCOMMITTED相同。您可以通过运行Read Unitted的代码和查询窗口2的代码来看到这一点,然后运行以下代码:

SELECT * FROM dbo.IsolationTests WITH (NOLOCK);

如果您决定实现快照隔离,并且您的当前代码正在使用NOLOCK(或READUNCOMMITTED)表提示,这些指定的提示将具有优先级-您将需要更改代码以获得使用快照隔离级别的好处。

总结

在ISO隔离级别下,当我们更改查询运行的隔离级别时,我们要么减少锁(但允许读取脏数据),要么增加所涉及的锁定以最小化并发效果。快照隔离级别消除了所有并发效果,同时在读取事务上保持零阻塞,但由于没有任何东西是免费的,因此您要付出的代价是增加了temdb活动并增加了对用户数据库和temdb中存储空间的需求。尽管如此,我觉得如果您使用的是Read Unitted(或nolock),则应该切换到使用Read提交快照隔离级别,以实现您试图为该查询实现的无阻塞。

提示

数据库引擎(BOL)中的隔离级别 http://technet.microsoft.com/en-us/library/ms189122%28v=sql.105%29.aspx

设置事务隔离级别(BOL)  http://technet.microsoft.com/en-us/library/ms173763%28v=sql.105%29.aspx

并发效应(BOL) http://technet.microsoft.com/en-us/library/ms190805%28v=sql.105%29.aspx

猜你喜欢

转载自www.cnblogs.com/hawking-520/p/9164867.html