[問題記錄] SELECT 產生共用鎖 LCK_M_S 造成 blocks

blocked in query text

紀錄一個最近解決的 query blocked 問題

某天開始突然一直收到 job step blocking message

查看了一下該 job 是把資料搬入歷史區,所異動的資料量頗多耗時長也不會再被變更

e.g. blocking query text

DECLARE @CopyDate datetime;
SET @CopyDate = dateadd(hour, - 4, GETUTCDATE());
INSERT INTO [History] (
	[Id]
	,[Name]
	,[Age]
	,[Phone]
	,[Address]
	,[DT]
	)
SELECT i.[Id]
	,p.[Name]
	,p.[Age]
	,i.[Phone]
	,i.[Address]
	,GETUTCDATE()
FROM [People] p
JOIN [Information] i 
ON i.[Id] = p.[MyId]
WHERE i.[DateTime] < @CopyDate

e.g. blocked query text

BEGIN TRAN
INSERT [dbo].[People] ([MyId] ,[Name] ,[Age] ,[Birthday] ,[Gender]) VALUES (@0 ,@1 ,@2 ,@3 ,@4 ,@5)
SELECT [MyId] FROM [dbo].[People] WHERE @@ROWCOUNT > 0 AND [MyId] = scope_identity()

透過模擬資料並查詢 sysprocesses 獲得 LCK_M_S 鎖

SELECT * FROM sysprocesses WHERE blocked <> 0 and spid > 50

從結果中發現 spid 74 (blocking) 使用 LCK_M_S 造成 spid 59 and 75 (blocked) 被鎖

為了避免資料搬動過程遭遇或產生 blocks 所以使用 WITH(NOLOCK) 允許 dirty read

e.g. after blocking query text

DECLARE @CopyDate datetime;
SET @CopyDate = dateadd(hour, - 4, GETUTCDATE());
INSERT INTO [History] (
	[Id]
	,[Name]
	,[Age]
	,[Phone]
	,[Address]
	,[DT]
	)
SELECT i.[Id]
	,p.[Name]
	,p.[Age]
	,i.[Phone]
	,i.[Address]
	,GETUTCDATE()
FROM [People] p WITH(NOLOCK)
JOIN [Information] i WITH(NOLOCK)
ON i.[Id] = p.[MyId]
WHERE i.[DateTime] < @CopyDate

e.g. after blocked query text

BEGIN TRAN
INSERT [dbo].[People] ([MyId] ,[Name] ,[Age] ,[Birthday] ,[Gender]) VALUES (@0 ,@1 ,@2 ,@3 ,@4 ,@5)
SELECT [MyId] FROM [dbo].[People] WITH(NOLOCK) WHERE @@ROWCOUNT > 0 AND [MyId] = scope_identity()

接下來的幾次模擬中就再也沒有發生 LCK_M_S 問題了

而 PAGELATCH 是 SQL Server 為了解決插入資料時物理層的衝突

當要修改 page 時 就必需先申請並取得一個 EX 的 latch 才能修改 page 內容
(PAGELATCH_EX = 獨佔, PAGELATCH_SH = 共用)

也因為資料頁修改都是在記憶體中完成 所以耗時短暫可被忽略

官方也有詳細的說明完整的鎖定相容性
https://technet.microsoft.com/en-us/library/ms186396(v=sql.105).aspx

have fun ʕ•͡ᴥ•ʔ