トランザクションとロックに関する私の知識を根本的に変える状況に遭遇しましたが(私はあまり知りません)、それを理解するための助けが必要です。
次のようなテーブルがあるとします。
CREATE TABLE [dbo].[SomeTable](
[Id] [bigint] IDENTITY(1,1) NOT NULL,
[SomeData] [varchar](200) NOT NULL,
[Moment] [datetime] NOT NULL,
[SomeInt] [bigint] NOT NULL
) ON [PRIMARY]
そして、私はこの「トランザクション内に1000行を挿入する」クエリを実行します。
BEGIN TRAN t1
DECLARE @i INT = 0
WHILE @i < 1000
BEGIN
SET @i = @i + 1
INSERT INTO [SomeTable] ([SomeData] ,Moment, SomeInt)
VALUES (CONVERT(VARCHAR(255), NEWID()), getdate(), @i)
WAITFOR DELAY '00:00:00:010'
END
COMMIT TRAN t1
このトランザクションが実行されている間、私は単純な選択を実行しています:
SELECT Id, Moment, SomeData, SomeInt FROM [SomeTable]
常にそれを再現できるとは限りません(どうやらタイミングに依存するようです)が、selectクエリは、挿入トランザクションが完了した後、1000行未満を返すことがあります。私の知らないところで、selectは常に1000行を返すと信じていましたが(分離レベルがRead Committedの場合)、トランザクションとロックがどのように機能するかを誤解しています。
ただし、(クラスター化インデックスを生成する)Id列に主キーを置くと、selectクエリは、私が試した限り、1000行すべてを返します。複合キーにクラスター化インデックスを使用し、他のいくつかの列に非クラスター化インデックスを使用して、インデックスを別の方法で配置すると、予想よりも少ない数の行が返される可能性があります。
だから、私はこれらの質問があります:
前もって感謝します。
コードを数回実行した後、これを再現することができませんでした。
ただし、ファイルの前のページに後の行が挿入されたときに発生する必要があると思います。
したがって、操作の順序は(たとえば)
表は10ページで構成されています。デフォルトでは、最初の8ページは混合エクステントから割り当てられ、次に均一エクステントが割り当てられます。多分あなたのケースでは、使用された混合エクステントより前の空き均一エクステントのためのスペースがファイルで利用可能でした。
この理論をテストするには、問題を再現した後で別のウィンドウで次のコマンドを実行し、元のSELECT
から欠落している行がすべてこの結果セットの先頭に表示されるかどうかを確認します。
SELECT [SomeData],
Moment,
SomeInt,
file_id,
page_id,
slot_id
FROM [SomeTable]
/*Undocumented - Use at own risk*/
CROSS APPLY sys.fn_PhysLocCracker(%% physloc %%)
ORDER BY page_id, SomeInt
インデックス付きテーブルに対する操作は、割り当ての順序ではなくインデックスキーの順序になるため、この特定のシナリオによる影響はありません。
インデックスに対して割り当て順スキャンを実行できますが、それは、テーブルが十分に大きく、分離レベルがコミットされずに読み取られるか、テーブルロックが保持されている場合にのみ考慮されます。
コミットされた読み取りは通常、データが読み取られるとすぐにロックを解放するため、インデックスに対するスキャンで行を2回読み取るか、まったく読み取らない可能性があります(インデックスキーが同時トランザクションによって更新され、行が前後に移動する場合)。 )このタイプの問題の詳細については、 Read Committed Isolation Level を参照してください。
ちなみに、元々、インデックスが挿入順序(Id、Moment、SomeIntのいずれか)に対して増加する列の1つにあるというインデックス付きのケースを想定していました。ただし、クラスター化インデックスがランダムSomeData
にある場合でも、問題は発生しません。
私は試した
DBCC TRACEON(3604, 1200, -1) /*Caution. Global trace flag. Outputs lock info
on every connection*/
SELECT TOP 2 *,
%%LOCKRES%%
FROM [SomeTable] WITH(nolock)
ORDER BY [SomeData];
SELECT *,
%%LOCKRES%%
FROM [SomeTable]
ORDER BY [SomeData];
/*Turn off trace flags. Doesn't check whether or not they were on already
before we started, with TRACEOFF*/
DBCC TRACEOFF(3604, 1200, -1)
結果は以下の通りでした
2番目の結果セットには、1,000行すべてが含まれます。ロック情報は、ロックが解放されたときにロックリソース24c910701749
での待機がブロックされていたとしても、それがスキャンを続行しないだけではないことを示しています。その時点から。代わりに、そのロックを直ちに解放し、新しい最初の行の行ロックを取得します。