I am using a table as a queue to process records. I use this query and sometimes the same ID is returned when it should never happen. I have reviewed pretty much everything and cannot find the reason why it returns the same ID for different worker.
In this case I have 8 workers retrieving the next batch to process and once in a while I get an error that the batch was already processed. My understanding is that the ID returned by the query should never be the same for a different worker running the same query.
Please advise!
BEGIN TRANSACTION
UPDATE TOP(1)
tblBatch WITH (ROWLOCK, UPDLOCK, READPAST)
SET
BatchStatusID = 2
OUTPUT
inserted.BatchID
FROM
tblBatch
WHERE
tblBatch.BatchID IN (
SELECT
tblBatch.BatchID
FROM
tblBatch
INNER JOIN tblBatchName ON tblBatch.BatchID = tblBatchName.BatchID
WHERE
(tblBatch.BatchStatusID = 1)
GROUP BY
tblBatch.BatchID
HAVING
COUNT(tblBatchName.BatchID) >= 1 AND COUNT(tblBatchName.BatchID) <= 2147483647
)
COMMIT
I added the ROWLOCK in addition to the UPDLOCK and READPAST and use a transaction. This made no difference.
Your primary issue is that the inner subquery referencing
tblBatchalso needs the locking hintsWITH (ROWLOCK, UPDLOCK, READPAST).You also need to make sure that it's going to lock directly on the correct rows. You need an index as follows
But then it looks like you would probably be better of using a window function and updating the subquery directly.
Also the join doesn't seem to be doing anything so you could probably remove it.
But then it makes no sense anyway. Assuming
BatchIdis the primary key, the count will always be exactly1. AndCOUNTcan never be zero anyway, and cannot be more than the max of anintvalue, so the count check is pointless.So you might as well just forget about it and using a single level update.
Note that an explicit transaction is not necessary: a single statement is atomic anyway.