Если я понимаю мотивацию вопроса, вы хотите избежать вероятности того, что две параллельные транзакции могут одновременно выполнить подзапрос, чтобы обработать верхние N строк, а затем перейти к обновлению тех же строк?
Вв этом случае я бы использовал этот подход.
;WITH cte As
(
SELECT TOP(@numberToProcess)
*
FROM
ToBeProcessed WITH(UPDLOCK,ROWLOCK,READPAST)
WHERE
ToBeProcessed.IsBeingProcessed = 0
ORDER BY
ToBeProcessed.Id,
ToBeProcessed.Priority DESC
)
UPDATE
cte
SET
IsBeingProcessed = 1
OUTPUT
INSERTED.Id
INTO
#IdsToProcess
Ранее я был немного не уверен, будет ли SQL Server принимать U
блокировки при обработке вашей версии с подзапросом, таким образом блокируя две одновременные транзакции от чтения одного и того жеTOP N
строк.По-видимому, это не так.
Тестовая таблица
CREATE TABLE JobsToProcess
(
priority INT IDENTITY(1,1),
isprocessed BIT ,
number INT
)
INSERT INTO JobsToProcess
SELECT TOP (1000000) 0,0
FROM master..spt_values v1, master..spt_values v2
Тестовый сценарий (запуск в 2 одновременных сеансах SSMS)
BEGIN TRY
DECLARE @FinishedMessage VARBINARY (128) = CAST('TestFinished' AS VARBINARY (128))
DECLARE @SynchMessage VARBINARY (128) = CAST('TestSynchronising' AS VARBINARY (128))
SET CONTEXT_INFO @SynchMessage
DECLARE @OtherSpid int
WHILE(@OtherSpid IS NULL)
SELECT @OtherSpid=spid
FROM sys.sysprocesses
WHERE context_info=@SynchMessage and spid<>@@SPID
SELECT @OtherSpid
DECLARE @increment INT = @@spid
DECLARE @number INT = @increment
WHILE (@number = @increment AND NOT EXISTS(SELECT * FROM sys.sysprocesses WHERE context_info=@FinishedMessage))
UPDATE JobsToProcess
SET @number=number +=@increment,isprocessed=1
WHERE priority = (SELECT TOP 1 priority
FROM JobsToProcess
WHERE isprocessed=0
ORDER BY priority DESC)
SELECT *
FROM JobsToProcess
WHERE number not in (0,@OtherSpid,@@spid)
SET CONTEXT_INFO @FinishedMessage
END TRY
BEGIN CATCH
SET CONTEXT_INFO @FinishedMessage
SELECT ERROR_MESSAGE(), ERROR_NUMBER()
END CATCH
Практически сразу выполнение останавливается, так какобе параллельные транзакции обновляют одну и ту же строку, поэтому блокировка S
, полученная при идентификации TOP 1 priority
, должна быть снята до того, как она получит блокировку U
, после чего 2 транзакции продолжат последовательно получать блокировку U
и X
.

Если добавлен элемент конфигурации ALTER TABLE JobsToProcess ADD PRIMARY KEY CLUSTERED (priority)
, то вместо этого возникает почти мгновенная взаимоблокировка, так как в этом случае блокировка строки S
не снимается, одна транзакция получаетU
блокирует строку и ожидает ее преобразования в X
блокировку, а другая транзакция все еще ожидает преобразования ее S
блокировки в U
блокировку.

Если приведенный выше запрос изменен на использование MIN
вместо TOP
WHERE priority = (SELECT MIN(priority)
FROM JobsToProcess
WHERE isprocessed=0
)
, то SQL Server удается полностью исключить подзапрос из плана и принимает U
, блокирует всепуть.
