I’ve got a simple queue implementation in MS Sql Server 2008 R2. Here’s the essense of the queue:
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
)
I want to atomically select the top n rows ordered by the priority and the id where IsBeingProcessed is false and update those rows to say they are being processed. I thought I’d use a combination of Update, Top, Output and Order By but unfortunately you can’t use top and order by in an Update statement.
So I’ve made an in clause to restrict the update and that sub query does the order by (see below). My question is, is this whole statement atomic, or do I need to wrap it in a transaction?
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
Here’s some sql to insert some dummy rows:
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'');
If I understand the motivation for the question you want to avoid the possibility that two concurrent transactions could both execute the sub query to get the top N rows to process then proceed to update the same rows?
In that case I’d use this approach.
I was a bit uncertain earlier whether SQL Server would take
Ulocks when processing your version with the sub query thus blocking two concurrent transactions from reading the sameTOP Nrows. This does not appear to be the case.Test Table
Test Script (Run in 2 concurrent SSMS sessions)
Almost immediately execution stops as both concurrent transactions update the same row so the
Slocks taken whilst identifying theTOP 1 prioritymust get released before it aquires aUlock then the 2 transactions proceed to get the rowUandXlock in sequence.If a CI is added
ALTER TABLE JobsToProcess ADD PRIMARY KEY CLUSTERED (priority)then deadlock occurs almost immediately instead as in this case the rowSlock doesn’t get released, one transaction aquires aUlock on the row and waits to convert it to anXlock and the other transaction is still waiting to convert itsSlock to aUlock.If the query above is changed to use
MINrather thanTOPThen SQL Server manages to completely eliminate the sub query from the plan and takes
Ulocks all the way.