部分的に更新された行を読みますか?


15

SSMSの2つの個別のセッションで実行される2つのクエリがあるとします。

最初のセッション:

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

2番目のセッション:

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

SELECTステートメントが半分更新された行、たとえばName = 'Jonny'andを含む行を読み取ることは可能Surname = 'Goody'ですか?

クエリは、別々のセッションでほぼ同時に実行されます。

回答:


22

はい、SQL Serverは、状況によっては、「古い」バージョンの行から1つの列の値を読み取り、「新しい」バージョンの行から別の列の値を読み取ることができます。

セットアップ:

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 

2番目の接続で、これを実行します。

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クエリは非クラスタ化インデックスではなく(ヒントが原因とはいえ)クラスタ化インデックスから列を取得しています。

ここに画像の説明を入力してください

更新ステートメントは、幅広い更新計画を取得します...

ここに画像の説明を入力してください

...インデックスを順番に更新して、あるインデックスから「前」の値を読み取り、別のインデックスから「後」の値を読み取ることができるようにします。

同じ列値の2つの異なるバージョンを取得することもできます。

最初の接続で、これを実行します:

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   

そして、2番目に、これを実行します:

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のインデックスなし)と、質問のような実行される2つのクエリがある場合、私は正しいですか別のセッションで、更新された行または古い行を取得しますが、行の更新の中間結果はありませんか?
テシュ

@Teshはい、他の結果はすべて同じページにあり、書き込み中にラッチで保護されるため、他の結果を得ることができないと思います。
マーティンスミス

WITH (NLOCK)ヒントを使って予期せぬことが起こるのはあなた自身のせいです。NOLOCKヒントなしでこれを行うことはできますか?
ロスプレッサー

2
@RossPresser-最初の例に該当します。インデックスの交差部分はこちらblogs.msdn.com/b/craigfr/archive/2007/05/02/…をご覧ください。2番目の場合、2つの異なるコミットバージョンが利用可能であれば、可能性があると思います。実際に設計できるかどうかはわかりません。
マーティンスミス
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.