Использование таблицы в качестве очереди работы для нескольких процессов в SQL 2005 - PullRequest
2 голосов
/ 13 октября 2010

Учитывая таблицу (JobTable), которая имеет 2 столбца: JobId, JobStatus (есть другие, но я их исключаю, поскольку они не влияют на вопрос).

Aпроцесс, WorkGenerator , INSERTs строк в таблице.Другой процесс, Worker , выполняет хранимую процедуру с именем GetNextJob.

Прямо сейчас GetNextJob делает ВЫБРАТЬ , чтобы найти следующую часть работы ( JobStatus = 1 ), а затем ОБНОВЛЕНИЕ допометьте эту работу как незавершенную ( JobStatus = 2 ).

Мы стремимся к увеличению, имея несколько процессов Worker , но обнаружили, что это возможно длянесколько работников, чтобы взять одну и ту же работу.

У меня есть следующие запросы:

  • В GetNextJob можно ли объединить SELECT и UPDATE в один запрос и использоватьпредложение OUTPUT, чтобы получить JobId?
  • Как я могу гарантировать, что только 1 процесс подберет каждую часть работы?

Я ценю ответы, которые работают, но также объяснения, почемуони работают.

Ответы [ 2 ]

2 голосов
/ 13 октября 2010

Позволяет создать решение:

Убедитесь, что ОБНОВЛЕНИЕ проверяет @@ ROWCOUNT

Проверка @@ ROWCOUNT после UPDATEчтобы определить, какой рабочий процесс выиграет.

CREATE PROCEDURE [dbo].[GetNextJob] 
AS
BEGIN
    SET NOCOUNT ON;

    DECLARE @jobId INT

    SELECT TOP 1 @jobId = Jobs.JobId FROM Jobs
    WHERE Jobs.JobStatus = 1
    ORDER BY JobId ASC

    UPDATE Jobs Set JobStatus = 2
    WHERE JobId = @jobId
    AND JobStatus = 1;

    IF (@@ROWCOUNT = 1)
    BEGIN
        SELECT @jobId;
    END
END

GO

Обратите внимание, что в случае описанной выше процедуры процесс, который не выиграл, не возвращает никаких строк и должен снова вызвать процедуру, чтобы получить следующийrow.

Вышеприведенное исправит большинство случаев, когда оба Рабочих берут одну и ту же работу, потому что UPDATE защищает от этого.Однако @@ ROWCOUNT может иметь значение 1 для обоих работников для одного и того же идентификатора задания!

Блокировка строки в транзакции, чтобы только 1 работник мог обновить статус

CREATE PROCEDURE [dbo].[GetNextJob] 
AS
BEGIN
    SET NOCOUNT ON;

    BEGIN TRANSACTION

        DECLARE @jobId INT

        SELECT TOP 1 @jobId = Jobs.JobId FROM Jobs WITH (UPDLOCK, ROWLOCK)
        WHERE Jobs.JobStatus = 1
        ORDER BY JobId ASC

        UPDATE Jobs Set JobStatus = 2
        WHERE JobId = @jobId
        AND JobStatus = 1;

        IF (@@ROWCOUNT = 1)
        BEGIN
            SELECT @jobId;
        END

    COMMIT
END

GO

Требуются как UPDLOCK, так и ROWLOCK.UPDLOCK в SELECT говорит MSSQL заблокировать строку, как будто она обновляется до фиксации транзакции.ROWLOCK (вероятно, не обязательно), но говорит MSSQL блокировать только ROW, возвращаемый SELECT.

Оптимизация блокировки

Когда 1 процесс использует подсказку ROWLOCKчтобы заблокировать строку, другие процессы блокируются в ожидании снятия блокировки.Можно указать подсказку READPAST.Из MSDN:

Если указано READPAST, блокировки как на уровне строки, так и на уровне страницы пропускаются.То есть компонент Database Engine пропускает строки или страницы вместо блокирования текущей транзакции до тех пор, пока блокировки не будут сняты.

Это остановит блокирование других процессов и повысит производительность.

CREATE PROCEDURE [dbo].[GetNextJob] 
AS
BEGIN
    SET NOCOUNT ON;

    BEGIN TRANSACTION
        DECLARE @jobId INT

        SELECT TOP 1 @jobId = Jobs.JobId FROM Jobs WITH (UPDLOCK, READPAST)
        WHERE Jobs.JobStatus = 1
        ORDER BY JobId ASC

        UPDATE Jobs Set JobStatus = 2
        WHERE JobId = @jobId
        AND JobStatus = 1;

        IF (@@ROWCOUNT = 1)
        BEGIN
            SELECT @jobId;
        END

    COMMIT
END

GO

Для рассмотрения: объедините SELECT и Update

Объедините SELECT и UPDATE и используйте SET для получения идентификатора.

Например:

DECLARE @val int

UPDATE JobTable 
SET @val = JobId, 
status = 2 
WHERE rowid = (SELECT min(JobId) FROM JobTable WHERE status = 1) 

SELECT @val 

Это все еще требует, чтобы транзакция была SERIALIZABLE, чтобы каждая строка была выделена только одному рабочему.

Для рассмотрения: снова объедините SELECT и UPDATE

Объедините SELECT и UPDATE и используйте предложение Output.

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