翻译13

事务隔离级别

 

我最近有篇文章锁定、阻塞和死锁在这里发表对SQL Server的中央。本文继续从那里讨论事务隔离级别,以及如何对事务隔离级别的选择影响锁紧机构在文章前面所讨论的。

如果我们看看书在线(BOL)的话题数据库引擎中的隔离级别,我们可以看到,一个事务隔离级别控制:

是否占用锁读数据时,与所请求的锁类型。

多久读锁举行。

读取操作是否引用其他事务修改的行:

块到行的独占锁被释放。

检索提交的版本,当时存在的语句或事务开始的行。

读取未提交的数据修改。

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

 

ISO的隔离级别

下表显示了不同的ISO的隔离级别,和并发的副作用的人:

Isolation Level

Dirty Reads?

Nonrepeatable Reads?

Phantom Reads?

Missing / Double Reads?

Read uncommitted

Yes

Yes

Yes

Yes

Read committed

No

Yes

Yes

Yes

Repeatable Read

No

No

Yes

No

Serializable

No

No

No

No

 

当我们审视这个表,我们可以看到,不同的事务隔离级别是为了消除并发的影响。

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

Isolation Level

Dirty Reads?

Nonrepeatable Reads?

Phantom Reads?

Missing / Double Reads?

Read committed snapshot

No

No

No

No

Snapshot

No

No

No

No

 

read_committed_snapshot是数据库级别的设置,如果它被打开的事务隔离级别是读取,然后将使用行版本控制目前的数据的事务一致性视图的时候,语句开始。

快照隔离级别也使用行版本控制目前的数据的事务一致性视图的时候,语句开始。这就要求,allow_snapshot_isolation数据库设置被打开,并发出设置快照隔离级别的查询语句。

在这些快照隔离级别,影响的是读者不阻止作家和作家不阻止读者。此外,读者将无法读取任何飞行数据修改从其他交易。

正如我已经提到的,这些都使用行版本控制。使用行版本控制的时候,在SQL Server数据库引擎将保持行受交易版本。使用行版本控制将:

消除共享锁在读事务

降低阻塞

增加资源进行数据修改,需要

增加活动

所有数据库的数据修改都将行版本控制

每个数据记录将有14字节记录后缀添加到

 

并发的影响

上述图表提几个不同的并发副作用,所以让我们解释这些。这些影响被定义在BOL在并发的影响:

 

脏读(参考ISO为“自由依赖性”)发生在第二交易选择一行被更新的另一个事务。一个脏读时发生修改的数据是在其他交易实际上读条,修改数据的事务。如果这个事务被回滚,然后第二交易刚刚返回一行数据,在数据库中不存在。这种效应可以通过预防避免阅读数据正在改变。

不可重复读(参考ISO“矛盾分析”)发生在当一个事务读取同一行多次,结果是不同的不同的读。这发生在当一个事务修改并提交对行的更改。而类似于脏读,不同的是,不可重复读写事务成功提交的事务,而在一个肮脏的读写事务回滚。这种效应可以通过防止数据的变化到数据的读取已经完成了。

幻读时发生的交易,是读数据读一系列数据,而另一个事务插入或删除行。如果通过阅读交易发表的声明被发表了,会有额外的行返回(用于插入交易),或较少的行返回(用于删除交易)。这种效应可以通过阻止交易的插入或删除数据时,数据被读取时避免。

失踪/双读发生时:

阅读是阅读交易范围的索引中的行扫描操作,并在读一行由二事务更新,改变索引键列(S)和其在扫描位置。如果更新移动一行从扫描开始的结束,阅读交易可以错过阅读行;相反如果更新移动行从扫描到最后一行,可以读两遍开始。

如果在未提交读隔离级别阅读交易执行分配顺序扫描(使用IAM页)和另一个交易导致页拆分,行可没有被阅读事务读取。

当你读过这些效果,你应该能够看到你的工作,以防止这些并发的效果,你是创造更多的锁定(因此更可能阻碍)在数据库。

 

对并发的影响实例

 

让我们来看一些例子来看看这些不同的并发性的影响表现在不同的事务隔离级别。所有这些例子中使用两个查询窗口;将运行一个读写事务的事务而另一个运行。查询利用“WAITFOR DELAY”给你一点点的时间开始一个事务,并切换到其他查询窗口运行另一个。

首先是数据库的初始化代码。这个代码需要运行到运行每个测试之前。它被放在一个存储过程,以便它可以很容易地运行是必要的。

IF DB_ID('IsolationLevelTest')ISNOTNULLBEGIN

    USEIsolationLevelTest;

    ALTERDATABASEIsolationLevelTest SETSINGLE_USER WITHROLLBACK IMMEDIATE;

    USE master;

    DROPDATABASEIsolationLevelTest;

END;

CREATEDATABASE IsolationLevelTest;

GO

USE IsolationLevelTest;

GO

 

CREATEPROCEDURE dbo.db_reset AS

IF OBJECT_ID('dbo.IsolationTests','U')ISNOTNULLDROPTABLE dbo.IsolationTests;

CREATETABLE dbo.IsolationTests (   

    Id      INTEGER IDENTITY,   

    ColA    CHAR(1));

INSERTINTO dbo.IsolationTests(ColA)

SELECT'A'UNIONALL

SELECT'A'UNIONALL

SELECT'A'UNIONALL

SELECT'A'UNIONALL

SELECT'A'UNIONALL

SELECT'A'UNIONALL

SELECT'A';

 

SELECT  *

FROM    dbo.IsolationTests;

 

IFEXISTS(SELECT 1 FROM sys.databases WHERE database_id = DB_ID('IsolationLevelTest')ANDsnapshot_isolation_state = 1)

    ALTERDATABASEIsolationLevelTest SETALLOW_SNAPSHOT_ISOLATION OFF;

GO

EXECUTE dbo.db_reset;

GO

 

未提交读

在未提交读隔离级别,我们将看看如何允许脏读。这将开始在一个查询窗口交易进行更新,同时在另一个查询窗口运行SELECT语句在读取未提交的事务隔离级别,以便查询将读取的数据被修改。一段时间后,在第一个查询窗口的事务回滚。你会看到第二查询窗口回来了,从来没有致力于表中的数据。

 

1.在第一个查询窗口,运行这些报表:

USE IsolationLevelTest;

GO

EXECUTE dbo.db_reset;

GO

 

BEGINTRANSACTION;

UPDATE  dbo.IsolationTests

SET     ColA ='Z';

--Simulate having some intensive processing here with await

WAITFOR DELAY '00:00:10';

ROLLBACK;

GO

 

SELECT*

FROM dbo.IsolationTests;

GO

2.第二查询窗口,运行这些报表:

USE IsolationLevelTest;

GO

-- READ UNCOMMITTED

-- Run this in query window 2 while the 1st query isrunning

SETTRANSACTION ISOLATION LEVEL READUNCOMMITTED;

SELECT*FROM dbo.IsolationTests;

3.从结果中可以看出,第二查询立即返回,并返回,随后被回滚在第一个查询窗口的值。

提交读

 

在读取测试中,我们将重新运行这些语句。第二查询窗口设置为使用READ COMMITTED隔离级别。因此,选择语句第二查询窗口运行要等到第一个完成交易(事务提交或回滚)才可以阅读它被公开交易数据–受阻。

 

1.在第一个查询窗口,运行这些报表:

USE IsolationLevelTest;

GO

EXECUTE dbo.db_reset;

GO

 

BEGINTRANSACTION;

UPDATE  dbo.IsolationTests

SET     ColA ='Y';

--Simulate having some intensive processing here with await

WAITFOR DELAY '00:00:10';

ROLLBACK;

2.第二查询窗口,运行这些报表:

USE IsolationLevelTest;

GO

-- READ COMMITTED

-- Run this in query window 2 while the 1st query isrunning

SETTRANSACTION ISOLATION LEVEL READCOMMITTED;

SELECT*FROM dbo.IsolationTests;

3.你可以看到,在查询窗口2报表等查询窗口1交易完成之前,它可以运行,并返回查询窗口查询窗口2 1完成后的表中的值。

 

可重复读

 

为下一个隔离级别,可重复读,我们将展示如何在这个隔离级别,从表中两次读取数据的事务,与读了一段时间,必须返回相同的数据。该隔离级别下,它必须读完全相同的数据,读取数据,那么它将阻止二事务试图更新这些行。我们将从可重复读致力于展示允许更新运行的影响这一变化。

 

1.在第一个查询窗口,运行这些报表:

USE IsolationLevelTest;

GO

EXECUTE dbo.db_reset;

GO

 

SETTRANSACTION ISOLATION LEVEL REPEATABLE READ;

BEGINTRANSACTION;

SELECT*FROM dbo.IsolationTests;

WAITFOR DELAY '00:00:10';

SELECT*FROM IsolationTests;

ROLLBACK;

2.第二查询窗口,运行这些报表:

USE IsolationLevelTest;

GO

UPDATE dbo.IsolationTests SETCol1 = -1;

3.注意,查询窗口查询窗口2等到1完成,因为查询窗口1在可重复读。

4.重复步骤1-3:

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

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

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

 

序列化

 

在可重复读测试刚刚完成,我们看到更新的数据被阻止。SERIALIZABLE隔离级别采取这一步,还可以防止插入或删除从发生到这表。为了测试这一点,我们将从根本上重新运行测试可重复读,我们将隔离级别更改为可序列化并试图执行插入而更新。我们将在可重复读隔离级别运行这个测试,如何不让插入运行。

 

1.在第一个查询窗口,运行这些报表:

USE IsolationLevelTest;

GO

EXECUTE dbo.db_reset;

GO

 

-- SERIALIZABLE

-- Run this in query window 1

SETTRANSACTION ISOLATION LEVEL SERIALIZABLE;

--SET TRANSACTION ISOLATION LEVEL REPEATABLE READ;  -- what will happen if this is set instead?

BEGINTRANSACTION;

SELECT*FROM dbo.IsolationTests;

WAITFOR DELAY '00:00:10';

SELECT*FROM dbo.IsolationTests;

ROLLBACK;

2.第二查询窗口,运行这些报表:

USE IsolationLevelTest;

GO

INSERTINTO dbo.IsolationTests(ColA)

VALUES('W');

3.注意,在查询窗口2插入等待直到在查询窗口1交易完成。

4.重复步骤1-3:

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

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

5.注意,在查询窗口2插入马上跑,和第二SELECT语句查询窗口1返回插入行。

 

快照

 

已提交读取/未提交的事务的水平,我们看着也有失踪/双读取的问题。可重复读序列化隔离级别消除这个问题,但这样做严重的其他交易阻塞罚下。快照隔离级别,消除所有相同的并发副作用SERIALIZABLE隔离级别,并没有引入锁定(从而消除阻塞)。在这个测试中,我们将首先显示在快照隔离级别的无阻塞,并显示如何将这些相同的语句被使用SERIALIZABLE隔离级别时。

 

1.第一个查询窗口,运行这些报表:

USE IsolationLevelTest;

GO

EXECUTE dbo.db_reset;

GO

 

-- SNAPSHOT

ALTERDATABASE IsolationLevelTest SET ALLOW_SNAPSHOT_ISOLATION ON;

GO

-- Run this in query window 1

USE IsolationLevelTest;

GO

SETTRANSACTION ISOLATION LEVEL SNAPSHOT;

BEGINTRANSACTION;

SELECT*FROM dbo.IsolationTests;

WAITFOR DELAY '00:00:10';

SELECT*FROM dbo.IsolationTests;

ROLLBACK;

2.第二查询窗口,运行这些报表:

USE IsolationLevelTest;

GO

INSERTINTO dbo.IsolationTests(ColA)

VALUES('X');

SELECT*FROM dbo.IsolationTests;

3.注意,查询窗口2立即完成,但数据修改不会反映在查询窗口1。

4.如果你改变了查询窗口1使用SERIALIZABLE隔离级别并重新运行测试,你会看到,查询窗口2现在将被封锁,现在等待查询窗口1之前已经可以完成插入行。

 

如何使用NOLOCK查询提示不适合吗?

 

表提示NOLOCK(为表提示Readuncommitted相同)是指定事务级READUNCOMMITTED相同。你可以通过运行代码读取未提交的看到这一点,和查询窗口运行下面的代码而不是2:

 

SELECT*FROM dbo.IsolationTests WITH(NOLOCK);

如果你想实现快照隔离,和你当前的代码是使用NOLOCK(或READUNCOMMITTED)表提示,这些提示将指定优先级–你需要改变代码来获取使用快照隔离级别的好处。

 

概要

 

ISO隔离级别下,当我们改变一个运行查询从SQL Server默认已提交读隔离级别,我们要么减少锁(但允许脏数据的读取),或增加锁定参与减少并发的影响。快照隔离级别,消除所有的并发影响而保持零阻塞读事务,但是没有什么是免费的,你支付的价格增加tempdb的活性和增加存储空间的需求在用户数据库和tempdb。如此说来,我觉得如果你正在使用未提交读(或NOLOCK),你应该使用READ COMMITTED SNAPSHOT隔离级别来实现无阻塞你试图实现查询。

 

 

猜你喜欢

转载自blog.csdn.net/xtt_3170707038/article/details/80644585