Reputation: 15860
I have a table called "Schedule" that serves as a queue mechanism for multiple processes/computers hitting the table on a frequent basis. The goal of the proc is to pick up rows no more then @count of rows, that qualify to be picked up (their LastCompletedProcessingId & LastStartedProcessingId's must match) and mark them as having been picked up (change LastStartedProcessingId to NEWID()), so that the next process doesn't try to pick up already marked rows.
My issue is that once in a rare while when the proc is called at very near times by multiple clients, multiple clients end up with the same rows. How is this possible? How do I avoid it? The table itself is not huge. @timeout is not an issue as these things do not take more then 300seconds to process, and i have a log that prior to the processes picking up multiple records, they didnt run over 300seconds. This is running in SQL Azure
Any thoughts of how this can be possible? Thanks
CREATE PROCEDURE X
@count int,
@timeout int = 300
AS
BEGIN
SET NOCOUNT ON;
DECLARE @batchId uniqueidentifier
SELECT @batchId = NEWID()
BEGIN TRAN
-- Update rows
UPDATE Schedule
WITH (ROWLOCK)
SET
LastBatchId = @batchId,
LastStartedProcessingId = NEWID(),
LastStartedProcessingTime = GETDATE()
WHERE
AccountId IN (
SELECT TOP (@count) AccountId
FROM Schedule
WHERE
(LastStartedProcessingId = LastCompletedProcessingId OR LastCompletedProcessingId IS NULL OR DATEDIFF(SECOND, LastStartedProcessingTime, GETDATE()) > @timeout) AND
(LastStartedProcessingTime IS NULL OR DATEDIFF(SECOND, LastStartedProcessingTime, GETDATE()) > Frequency)
ORDER BY (DATEDIFF(SECOND, LastStartedProcessingTime, GETDATE()) - Frequency) DESC
)
-- Return the changed rows
SELECT AccountId, LastStartedProcessingId, Frequency, LastProcessTime, LastConfigChangeTime
FROM Schedule
WHERE LastBatchId = @batchId
COMMIT TRAN
END
Upvotes: 0
Views: 123
Reputation: 1240
Instead of using the ROWLOCK hint, run your update with the isolation_level set to SERIALIZABLE.
Also, if you use an OUTPUT clause in your update, you can have the list of affected rows as soon as the update is complete. That means you can end your transaction 1 DML statement sooner and maintain your ACIDity.
Upvotes: 1