T-SQL - это дополнительное запрос для рестрикции обновления атомным обновлением?
-
28-09-2019 - |
Вопрос
У меня есть простая реализация очереди в MS SQL Server 2008 R2. Вот эссенкт очереди:
CREATE TABLE ToBeProcessed
(
Id BIGINT IDENTITY(1,1) PRIMARY KEY NOT NULL,
[Priority] INT DEFAULT(100) NOT NULL,
IsBeingProcessed BIT default (0) NOT NULL,
SomeData nvarchar(MAX) NOT null
)
Я хочу аторично выбирать лучшие n строки, заказанные приоритетом и идентификатору, где iDBEAGESPROCED является ложным и обновлять эти строки, чтобы сказать, что они обрабатываются. Я думал, что буду использовать комбинацию обновления, верхнего, вывода и порядка, но, к сожалению, вы не можете использовать верх и заказывать в операторе обновления.
Итак, я сделал предложение, чтобы ограничить обновление, и этот подряд делает заказ (см. Ниже). Мой вопрос в том, это все это утверждение атомное, или мне нужно обернуть его в транзакции?
DECLARE @numberToProcess INT = 2
CREATE TABLE #IdsToProcess
(
Id BIGINT NOT null
)
UPDATE
ToBeProcessed
SET
ToBeProcessed.IsBeingProcessed = 1
OUTPUT
INSERTED.Id
INTO
#IdsToProcess
WHERE
ToBeProcessed.Id IN
(
SELECT TOP(@numberToProcess)
ToBeProcessed.Id
FROM
ToBeProcessed
WHERE
ToBeProcessed.IsBeingProcessed = 0
ORDER BY
ToBeProcessed.Id,
ToBeProcessed.Priority DESC)
SELECT
*
FROM
#IdsToProcess
DROP TABLE #IdsToProcess
Вот несколько SQL, чтобы вставить некоторые фиктивные строки:
INSERT INTO ToBeProcessed (SomeData) VALUES (N'');
INSERT INTO ToBeProcessed (SomeData) VALUES (N'');
INSERT INTO ToBeProcessed (SomeData) VALUES (N'');
INSERT INTO ToBeProcessed (SomeData) VALUES (N'');
INSERT INTO ToBeProcessed (SomeData) VALUES (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
Блокировка последовательно.
Если CI добавляется 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
Зафиксирует весь путь.
Другие советы
Каждый отдельный оператор T-SQL находится в соответствии со всем моим опытом и всем документом, который я когда-либо читал, должен быть атомным. То, что у вас есть, есть одно оператор T-SQL, ERGO должен быть атомным и не потребует явных операторов транзакций. Я использовал этот точный вид логики много раз, и никогда не имел проблем с этим. Я с нетерпением жду встречи с тем, если кто-то в качестве поддерживаемого альтернативного мнения.
Кстати, посмотрите в функции ранжирования, в частности, ROW_NUMBER (), для извлечения заданного количества элементов. Синтаксис, возможно, тапы неловко, но в целом они гибкие и мощные инструменты. (Есть о наложении базильона накладных вопросов и ответов, которые их обсуждают.)