有人可以解释为什么使用 nolock 进行选择会查询更新的数据吗?

Kin*_*han 4 sql-server select nolock

我正在阅读这里的答案(来自stackoverflow,我认为应该在这里提问)

NOLOCK 意味着根本不放置锁。

您的查询可能会在单个查询中返回 UPDATE 之前的部分数据和 UPDATE 之后的部分数据。

我知道 nolock 不会锁定表,所以其他人可以同时查询。

从它显示的答案和示例来看,它在数据更新时获取数据。

为什么会这样?

我假设对于普通选择,它会尝试在表上加锁,因此当执行更新语句时,它会在行或页上加锁。然后当我尝试运行 select 语句时,它无法放置锁,直到更新语句锁被释放。

但是在这种情况下因为select语句不会尝试给表加锁,所以它可以运行而无需等待update语句释放锁?

Mar*_*ith 10

这并不完全正确,这NOLOCK意味着根本不放置锁。在这个提示下的查询仍然需要Sch-S锁和(可能是 HOBT锁)。

read committed隔离级别下,SQL Server 将(通常)获取行级S锁并在读取数据后立即释放它们。这些与X未提交更新上的锁不兼容,因此可以防止脏读。

在链接答案的示例中,SELECT查询在遇到修改后的行时不会被阻止,因此很可能读取部分更新。

它也可能发生在默认read committed隔离级别,尽管 aSELECT读取一些具有“before”值的行而另一些具有“after”值的行。只需要设计一种情况

  1. 选择查询读取行的值R1并释放其S
  2. 更新查询更新R2并获取X
  3. 选择查询尝试读取R2并被阻止。
  4. 更新查询更新R1并获取X锁。
  5. 更新事务提交从而释放其锁并允许 Select 读取 R2

例如,如果SELECTUPDATE使用不同的索引来定位感兴趣的行,则可能会出现这种类型的情况。

例子

CREATE TABLE T
(
X INT IDENTITY PRIMARY KEY,
Y AS -X UNIQUE,
Name varchar(10),
Filler char(4000) DEFAULT 'X'
)


INSERT INTO T (Name)
SELECT TOP 2500 'A'
FROM master..spt_values
Run Code Online (Sandbox Code Playgroud)

现在在一个查询窗口中运行

DECLARE @Sum int

SELECT 'SET @@ROWCOUNT' WHERE 1=0

WHILE (@@ROWCOUNT = 0)
SELECT @Sum = SUM(LEN(Name))
FROM T 
WHERE Y IN (-1, -2500)
HAVING SUM(LEN(Name)) = 3
Run Code Online (Sandbox Code Playgroud)

这将无限循环运行。在另一个运行中

UPDATE T 
SET Name=CASE WHEN Name = 'A' THEN 'AA' ELSE 'A' END
Run Code Online (Sandbox Code Playgroud)

这可能会停止另一个查询中的循环(如果没有,请重试),这意味着它必须已读取A,AAAA,A