使用WITH(NOLOCK)和Transactions

Ran*_*der 3 t-sql sql-server sql-server-2012

说我有一个简单的查询,如

Select * From MyTable WITH (NOLOCK)
Run Code Online (Sandbox Code Playgroud)

并且,在执行此查询的同时,另一个用户在事务的上下文中向该表中插入100行.

从理论上讲,在提交或回滚事务之前,Select语句可能会读取插入到表中的100行的子集,因为它正在使用NOLOCK吗?如果我正确理解NOLOCK,这似乎是可能的,但想要验证.

SQL Server 2012

Aar*_*and 12

当然,您可以读取在事务开始和提交或回滚之间受影响的子集或全部未提交数据.这有点NOLOCK- 允许您读取未提交的数据,这样您就不必等待编写者,并避免放置大多数锁,以便编写者不必等待您.

证明#1

这很容易证明.在一个窗口中,创建此表:

CREATE TABLE dbo.what(id INT);
Run Code Online (Sandbox Code Playgroud)

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

DECLARE @id INT;

WHILE 1 = 1
BEGIN
 SELECT @id = id FROM dbo.what WITH (NOLOCK) WHERE id = 2;

 IF @id = 2
 BEGIN
   PRINT @id;
   BREAK;
 END
END
Run Code Online (Sandbox Code Playgroud)

现在回到第一个窗口,开始一个故意长时间运行的事务,但回滚:

BEGIN TRANSACTION;
GO

INSERT dbo.what SELECT 2;
GO 10000

ROLLBACK TRANSACTION;
Run Code Online (Sandbox Code Playgroud)

一旦在第一个窗口中启动它,第二个窗口中的查询将暂停,并将吐出已读取的未提交值.

证明#2

这主要是为了对@ Blam上面的评论提出质疑,我不同意这一点:

实际上我认为你可以在提交或回滚之前读取所有100个而不仅仅是一个子集.

您当然可以读取受事务影响的行的子集.尝试以下类似示例,这次将100个集合插入表中1000次,并使用查询查询计数(NOLOCK).窗口#1(如果您尚未测试上面的证明#1):

CREATE TABLE dbo.what(id INT);
Run Code Online (Sandbox Code Playgroud)

窗口#2:

DECLARE @c INT;

WHILE 1 = 1
BEGIN
 SELECT @c = COUNT(*) FROM dbo.what WITH (NOLOCK) WHERE id = 2;

 IF @c > 0
   PRINT @c;

 IF @c > 10000
   BREAK;
END
Run Code Online (Sandbox Code Playgroud)

回到窗口#1:

BEGIN TRANSACTION;
GO

INSERT dbo.what SELECT TOP (100) 2 FROM sys.all_objects;
GO 1000

ROLLBACK TRANSACTION;
Run Code Online (Sandbox Code Playgroud)

窗口#2将旋转,直到您开始交易.一旦你这样做,你就会开始看到计数涓涓细流.但它们不会是100的倍数(更不用说100,000,@Blam似乎正在制造的全部或全部声明).以下是我删节的结果:

1
10
12
14
17
19
23
25
29
...
85
87
91
95
98
100
100
...
9700
9700
9763
9800
9838
9900
9936
10000
10000
10000
10080
Run Code Online (Sandbox Code Playgroud)

NOLOCK查询显然不等待任何单个语句来读取数据之前完成,更不用说整个事务.因此,无论每个语句影响多少行,无论整个事务中有多少语句,您都可以获得任何流量状态的数据.

其他副作用

在某些情况下,NOLOCK可以跳过行,或者读取同一行两次,具体取决于扫描类型以及另一个事务生成页面拆分的时间.基本上发生的事情是,当(NOLOCK)查询正在读取数据时,其他写入实际上可以将数据移动到不同的位置 - 因为它们可以 - 将您已经读过的行移动到扫描中的前一点,或者移动一行即可在扫描的早期还没有读到一点.

忠告

一般来说,这是个坏消息,您应该考虑READ_COMMITTED_SNAPSHOT- 它具有允许读者不阻止编写器的相同好处,反之亦然,但在一个时间点为您提供一致的数据视图,忽略所有后续数据修改(这对tempdb有影响,所以一定要测试一下).这里非常详尽的信息.