Sql server 与(NOLOCK)和事务一起使用

Sql server 与(NOLOCK)和事务一起使用,sql-server,tsql,sql-server-2012,Sql Server,Tsql,Sql Server 2012,假设我有一个简单的查询,比如 Select * From MyTable WITH (NOLOCK) 并且,在执行该查询的同时,另一个用户正在事务上下文中向该表插入100行 从理论上讲,Select语句(因为它使用NOLOCK)是否可能在提交或回滚事务之前读取插入到表中的100行的子集?如果我对NOLOCK的理解正确,这似乎是一种可能性,但我想验证一下 SQL Server 2012当然,您可以读取在事务开始和提交或回滚之间受影响的未提交数据的子集或全部。这就是NOLOCK的一个要点,它允许您

假设我有一个简单的查询,比如

Select * From MyTable WITH (NOLOCK)
并且,在执行该查询的同时,另一个用户正在事务上下文中向该表插入100行

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


SQL Server 2012

当然,您可以读取在事务开始和提交或回滚之间受影响的未提交数据的子集或全部。这就是NOLOCK的一个要点,它允许您读取未提交的数据,这样您就不必等待写入程序,并且避免放置大多数锁,这样写入程序就不必等待您

证明#1

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

CREATE TABLE dbo.what(id INT);
在第二个窗口中,运行以下查询:

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
现在回到第一个窗口,启动一个有意长时间运行的事务,但将其回滚:

BEGIN TRANSACTION;
GO

INSERT dbo.what SELECT 2;
GO 10000

ROLLBACK TRANSACTION;
当您在第一个窗口中启动此操作时,第二个窗口中的查询将停止,并将吐出已读取的未提交值

证明#2

这主要是为了反驳@Blam的上述评论,我不同意:


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

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

窗口#2:

返回窗口#1:

窗口#2将旋转,直到您启动事务。一旦你这么做了,你就会开始看到统计数字慢慢流入。但它们不会是100的倍数(更不用说100000了,@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
NOLOCK
查询显然不会在读取数据之前等待任何一条语句完成,更不用说整个事务了。因此,无论每条语句影响多少行,也不管整个事务中有多少条语句,您都可以在任何流量状态下获取数据

其他副作用

还有一些情况下,
NOLOCK
可以,具体取决于扫描类型和另一个事务生成页面拆分的时间。本质上,当
(NOLOCK)
查询正在读取数据时,其他写入操作实际上可以将数据移动到不同的位置,因为它们可以将您已经读取的行移动到扫描的前一点,或者将您尚未读取的行移动到扫描的前一点

建议


一般来说,这是个坏消息,你应该考虑“代码> Read PrimeType快照< /Cord>”——它有相同的好处,允许读者不阻止写入器,反之亦然,但是在一个时间点给了你一个一致的数据视图,忽略了所有后续的数据修改(这对TEMPDB有影响,所以一定要测试它)。. .

正如Aaron优雅地解释的那样,是的,您将读取脏的未提交数据。但是,如果您想避免读取脏数据,并且还没有准备好开始使用乐观锁定,您可以尝试使用下面的表提示,尽管这样做的效果是它将跳过任何被锁定的行,因此您不会看到尚未提交的插入行和更新行

SELECT *
FROM MyTable WITH (READPAST)

请注意,此表提示要求数据库在读提交(默认)或可重复读隔离级别下运行。

实际上,我认为您可以在提交或回滚之前读取所有100,而不仅仅是子集。@BARM不同意。请看我最新的答案,这证明了事实并非如此。@AaronBertrand-我想你可能有误解。我将布拉姆的话解释为,除了读取受影响行的(适当的)子集外,您还可以在提交/回滚行之前读取整组行。@Martin我将其理解为“插入的所有100行,或者什么都没有”-毕竟,问题涉及另一个插入100行的事务。我也看到了你的解释,现在再读一遍,脑子里还想着你的评论,但还不清楚这是什么意思。是的,只有布拉姆可以肯定地证实@Randy顺便说一句,除了读取未提交行的子集,您甚至可以在中读取部分更新的列
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
BEGIN TRANSACTION;
GO

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

ROLLBACK TRANSACTION;
SELECT *
FROM MyTable WITH (READPAST)