T-SQL Является ли подзапрос для ограничения на обновление Atomic с обновлением? - PullRequest
7 голосов
/ 04 ноября 2010

У меня есть простая реализация очереди в 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 строк, упорядоченных по приоритету и идентификатору, где IsBeingProcessed имеет значение false, и обновить эти строки, чтобы сказать, что они обрабатываются.Я думал, что буду использовать комбинацию Update, Top, Output и Order By, но, к сожалению, вы не можете использовать top и order by в операторе Update.

Так что я сделал предложение in, чтобы ограничитьобновить, и этот подзапрос выполняет заказ (см. ниже).У меня вопрос, является ли весь этот оператор атомарным, или мне нужно обернуть его в транзакции?

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'');

Ответы [ 2 ]

8 голосов
/ 08 апреля 2011

Если я понимаю мотивацию вопроса, вы хотите избежать вероятности того, что две параллельные транзакции могут одновременно выполнить подзапрос, чтобы обработать верхние 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.

Heap

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

Clustered Index

Если приведенный выше запрос изменен на использование MIN вместо TOP

WHERE priority = (SELECT MIN(priority)
                   FROM JobsToProcess 
                   WHERE isprocessed=0 
                   )

, то SQL Server удается полностью исключить подзапрос из плана и принимает U, блокирует всепуть.

enter image description here

2 голосов
/ 04 ноября 2010

Каждое отдельное утверждение T-SQL, согласно моему опыту и всей документации, которую я когда-либо читал, должно быть атомарным. У вас есть один оператор T-SQL, поэтому он должен быть атомарным и не потребовать явных операторов транзакций. Я использовал эту точную логику много раз, и у меня никогда не было проблем с ней. Я с нетерпением жду возможности увидеть альтернативное мнение.

Кстати, посмотрите на функции ранжирования, в частности row_number (), для получения заданного количества элементов. Синтаксис, возможно, немного неловкий, но в целом они являются гибкими и мощными инструментами. (Существует около миллиарда вопросов и ответов, которые их обсуждают.)

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...