MERGE
ステートメント を実行するストアドプロシージャがあります。
マージを実行すると、デフォルトでテーブル全体がロックされるようです。
トランザクション内でこのストアドプロシージャを呼び出していますが、他の処理も行っており、影響を受ける行のみをロックしたいと思います。
ヒントMERGE INTO myTable WITH (READPAST)
を試しましたが、ロックが少ないようです。しかし、ms docには、主キーでさえもバイパスして重複キーを挿入する可能性があるという警告がありました。
これが私のテーブルスキーマです:
CREATE TABLE StudentDetails
(
StudentID INTEGER PRIMARY KEY,
StudentName VARCHAR(15)
)
GO
INSERT INTO StudentDetails
VALUES(1,'WANG')
INSERT INTO StudentDetails
VALUES(2,'JOHNSON')
GO
CREATE TABLE StudentTotalMarks
(
Id INT IDENTITY PRIMARY KEY,
StudentID INTEGER REFERENCES StudentDetails,
StudentMarks INTEGER
)
GO
INSERT INTO StudentTotalMarks
VALUES(1,230)
INSERT INTO StudentTotalMarks
VALUES(2,255)
GO
これが私のストアドプロシージャです:
CREATE PROCEDURE MergeTest
@StudentId int,
@Mark int
AS
WITH Params
AS
(
SELECT @StudentId as StudentId,
@Mark as Mark
)
MERGE StudentTotalMarks AS stm
USING Params p
ON stm.StudentID = p.StudentId
WHEN MATCHED AND stm.StudentMarks > 250 THEN DELETE
WHEN MATCHED THEN UPDATE SET stm.StudentMarks = p.Mark
WHEN NOT MATCHED THEN
INSERT(StudentID,StudentMarks)
VALUES(p.StudentId, p.Mark);
GO
これが私がロックを観察している方法です:
begin tran
EXEC MergeTest 1, 1
そして別のセッションで:
EXEC MergeTest 2, 2
2番目のセッションは、最初のセッションが完了するのを待ってから続行します。
クエリプロセッサに、StudentTotalMarks
レコードを見つけるためのより効率的なアクセスパスを与える必要があります。記述されているように、クエリでは、各行に残余述語[StudentID] = [@StudentId]
が適用されたテーブルのフルスキャンが必要です。
エンジンは、変換デッドロックの一般的な原因に対する基本的な防御策として、読み取り時にU
(更新)ロックを取得します。この動作は、最初の実行によってU
(排他的)ロックですでにロックされている行でX
ロックを取得しようとすると、2番目の実行がブロックされることを意味します。
次のインデックスは、不必要なU
ロックの取得を回避し、より優れたアクセスパスを提供します。
CREATE UNIQUE INDEX uq1
ON dbo.StudentTotalMarks (StudentID)
INCLUDE (StudentMarks);
クエリプランにStudentID = [@StudentId]
に対するシーク操作が含まれるようになったため、U
ロックはターゲット行に対してのみ要求されます。
インデックスは必須ではなく、当面の問題を解決するためにUNIQUE
にする必要があります(ただし、このクエリをカバーするインデックスにするためにはINCLUDE
が必要です)。
StudentID
テーブルのStudentTotalMarks
をPRIMARY KEY
にすると、アクセスパスの問題も解決します(明らかに冗長なId
列を削除できます)。常にUNIQUE
またはPRIMARY KEY
制約を使用して代替キーを適用する必要があります(正当な理由なしに無意味な代理キーを追加しないでください)。