阅读部分更新的行?


15

假设我有两个查询,分别在SSMS中的两个会话中运行:

第一届会议:

UPDATE Person
SET Name='Jonny', Surname='Cage'
WHERE Id=42

第二届会议:

SELECT Name, Surname
FROM Person WITH(NOLOCK)
WHERE Id > 30

SELECT语句是否可能读取半更新的行,例如带有Name = 'Jonny'和的行Surname = 'Goody'

查询几乎在单独的会话中同时执行。

Answers:


22

是的,在某些情况下,SQL Server可以从该行的“旧”版本中读取一列的值,并从该行的“新”版本中读取另一列的值。

设定:

CREATE TABLE Person
  (
     Id      INT PRIMARY KEY,
     Name    VARCHAR(100),
     Surname VARCHAR(100)
  );

CREATE INDEX ix_Name
  ON Person(Name);

CREATE INDEX ix_Surname
  ON Person(Surname);

INSERT INTO Person
SELECT TOP 1000000 ROW_NUMBER() OVER (ORDER BY @@SPID),
                   'Jonny1',
                   'Jonny1'
FROM   master..spt_values v1,
       master..spt_values v2 

在第一个连接中,运行以下命令:

WHILE ( 1 = 1 )
  BEGIN
      UPDATE Person
      SET    Name = 'Jonny2',
             Surname = 'Jonny2'

      UPDATE Person
      SET    Name = 'Jonny1',
             Surname = 'Jonny1'
  END 

在第二个连接中,运行以下命令:

DECLARE @Person TABLE (
  Id      INT PRIMARY KEY,
  Name    VARCHAR(100),
  Surname VARCHAR(100));

SELECT 'Setting intial Rowcount'
WHERE  1 = 0

WHILE @@ROWCOUNT = 0
  INSERT INTO @Person
  SELECT Id,
         Name,
         Surname
  FROM   Person WITH(NOLOCK, INDEX = ix_Name, INDEX = ix_Surname)
  WHERE  Id > 30
         AND Name <> Surname

SELECT *
FROM   @Person 

运行约30秒后,我得到:

在此处输入图片说明

SELECT查询正在从非聚集索引而不是聚集索引中检索列(尽管由于提示)。

在此处输入图片说明

更新语句获得广泛的更新计划 ...

在此处输入图片说明

...并按顺序更新索引,因此可以从一个索引中读取“之前”值,而从另一个索引中读取“之后”值。

也可以检索同一列值的两个不同版本。

在第一个连接中,运行以下命令:

DECLARE @A VARCHAR(MAX) = 'A';
DECLARE @B VARCHAR(MAX) = 'B';

SELECT @A = REPLICATE(@A, 200000),
       @B = REPLICATE(@B, 200000);

CREATE TABLE T
  (
     V VARCHAR(MAX) NULL
  );

INSERT INTO T
VALUES     (@B);

WHILE 1 = 1
  BEGIN
      UPDATE T
      SET    V = @A;

      UPDATE T
      SET    V = @B;
  END   

然后在第二步中运行以下命令:

SELECT 'Setting intial Rowcount'
WHERE  1 = 0;

WHILE @@ROWCOUNT = 0
  SELECT LEFT(V, 10)  AS Left10,
         RIGHT(V, 10) AS Right10
  FROM   T WITH (NOLOCK)
  WHERE  LEFT(V, 10) <> RIGHT(V, 10);

DROP TABLE T;

马上,这为我返回了以下结果

+------------+------------+
|   Left10   |  Right10   |
+------------+------------+
| BBBBBBBBBB | AAAAAAAAAA |
+------------+------------+

1
如果我有一个表CREATE TABLE Person(Id INT PRIMARY KEY,Name VARCHAR(100),Surname VARCHAR(100))(在Name和Surname上没有任何索引)和两个类似该问题的查询,这对吗?在单独的会话中,那么我将获得更新的行或旧的行,但没有更新该行的一些中间结果?
Tesh

@Tesh是的,我认为不可能获得任何其他结果,因为所有结果都在同一页上,并且在写入过程中受闩锁保护。
马丁·史密斯

任何带有WITH (NLOCK)提示的意外情况都是您自己的错。可以在没有NOLOCK提示的情况下发生吗?
Ross Presser 2015年

2
@RossPresser-是第一个示例,请参见索引相交部分,这里是blogs.msdn.com/b/craigfr/archive/2007/05/02/…。对于第二个,我想如果有两个不同的提交版本可用,可能会出现。不确定是否可以在实践中进行设计。
马丁·史密斯
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.