web-dev-qa-db-ja.com

UPDLOCKがSELECTをハング(ロック)させるのはなぜですか?

テーブル全体をロックする選択がSQL SERVERにあります。

これが設定スクリプトです(何も上書きしないようにしてください)

USE [master]
GO

IF EXISTS(SELECT 1 FROM sys.databases d WHERE d.name = 'LockingTestDB')
DROP DATABASE LockingTestDB
GO

CREATE DATABASE LockingTestDB
GO

USE [LockingTestDB]
GO
IF EXISTS(SELECT 1 FROM sys.tables t WHERE t.name = 'LockingTestTable')
  DROP TABLE LockingTestTable
GO

CREATE TABLE LockingTestTable (
  Id int IDENTITY(1, 1),
  Name varchar(100),
  PRIMARY KEY CLUSTERED (Id)
)
GO

INSERT INTO LockingTestTable(Name) VALUES ('1')
INSERT INTO LockingTestTable(Name) VALUES ('2')
GO

新しいクエリウィンドウを開き、次のトランザクション(待機中)を実行します。

USE [LockingTestDB]
GO

BEGIN TRANSACTION
  SELECT * FROM LockingTestTable t WITH (UPDLOCK, ROWLOCK) WHERE t.Name = '1'
  WAITFOR DELAY '00:01:00'

COMMIT TRANSACTION
--ROLLBACK
GO

USE [master]
GO

もう1つは実行されます(それらが同時に実行されることを確認してください)。

USE [LockingTestDB]
GO

SELECT * FROM LockingTestTable t WITH (UPDLOCK, ROWLOCK) WHERE t.Name = '2'

USE [master]
GO

2番目のクエリが最初のクエリによってブロックされることがわかります。最初のクエリを停止してROLLBACKを実行すると、2番目のクエリが完了します。

なんでこんなことが起こっているの?

PS:Nameに(フルカバレッジの)非クラスター化インデックスを追加すると修正されます。

USE [LockingTestDB]
GO

CREATE NONCLUSTERED INDEX [IX_Name] ON [dbo].[LockingTestTable] 
(
  [Name] ASC
)
INCLUDE ( [Id]) WITH (STATISTICS_NORECOMPUTE  = OFF, SORT_IN_TEMPDB = OFF, IGNORE_DUP_KEY = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS  = ON, ALLOW_PAGE_LOCKS  = ON) ON [PRIMARY]
GO

なぜですか?

13
marius-O

Books Onlineに記載 のように、UPDLOCKは更新ロックを取得し、トランザクションの最後まで保持します。

ロックする行を特定するためのインデックスがない場合、テストされたすべての行がロックされ、条件を満たす行のロックはトランザクションが完了するまで保持されます。

最初のトランザクションは、name = 1の行に更新ロックを保持します。2番目のトランザクションは、同じ行の更新ロックを取得しようとするとブロックされます(その行のname = 2かどうかをテストするため)。

インデックスを使用すると、SQL Serverは条件を満たす行のみをすばやく見つけてロックできるため、競合は発生しません。

ロックヒントの理由を検証し、適切なインデックスが存在することを確認するために、資格のあるデータベース専門家とコードを確認する必要があります。

関連情報: 読み取りコミットスナップショット分離でのデータ変更

19
Paul White 9