Ler uma linha parcialmente atualizada?

15

Digamos que eu tenha duas consultas, executando em duas sessões separadas no SSMS:

Primeira sessão:

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

Segunda sessão:

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

É possível que a SELECTinstrução possa ler uma linha semi-atualizada, por exemplo, uma com Name = 'Jonny'e Surname = 'Goody'?

As consultas são executadas quase simultaneamente em sessões separadas.

Tesh
fonte

Respostas:

22

Sim, o SQL Server pode, em algumas circunstâncias, ler o valor de uma coluna da versão "antiga" da linha e o valor de outra coluna da versão "nova" da linha.

Configuração:

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 

Na primeira conexão, execute o seguinte:

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

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

Na segunda conexão, execute o seguinte:

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 

Depois de executar por cerca de 30 segundos, recebo:

insira a descrição da imagem aqui

A SELECTconsulta está recuperando as colunas dos índices não agrupados em cluster em vez do índice agrupado (embora devido às dicas).

insira a descrição da imagem aqui

A declaração de atualização recebe um amplo plano de atualização ...

insira a descrição da imagem aqui

... e atualiza os índices em sequência, para que seja possível ler os valores "antes" de um índice e "depois" do outro.

Também é possível recuperar duas versões diferentes do mesmo valor de coluna.

Na primeira conexão, execute o seguinte:

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   

E então, no segundo, execute o seguinte:

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;

Imediatamente, isso retornou o seguinte resultado para mim

+------------+------------+
|   Left10   |  Right10   |
+------------+------------+
| BBBBBBBBBB | AAAAAAAAAA |
+------------+------------+
Martin Smith
fonte
1
Estou certo de que, se eu tiver uma tabela CREATE TABLE Person (ID INT PRIMARY KEY, Nome VARCHAR (100), Sobrenome VARCHAR (100)) (sem nenhum índice em Nome e Sobrenome) e duas consultas, como na pergunta, executadas em sessões separadas, receberei uma linha atualizada ou uma linha antiga, mas não algum resultado intermediário da atualização da linha?
Tesh
@ Sim, acho que não seria possível obter nenhum outro resultado, pois tudo estaria na mesma página e protegido por uma trava durante a gravação.
Martin Smith
Qualquer coisa inesperada que você recebe com uma WITH (NLOCK)dica é culpa sua. Isso pode acontecer sem uma NOLOCKdica?
Ross Presser
2
@RossPresser - Sim, para o primeiro exemplo, consulte a peça de interseção do índice aqui blogs.msdn.com/b/craigfr/archive/2007/05/02/… . Para o segundo, acho que seria possível se duas versões comprometidas diferentes estivessem disponíveis. Não tenho certeza se seria possível projetar na prática.
Martin Smith