Позволяет создать решение:
Убедитесь, что ОБНОВЛЕНИЕ проверяет @@ 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.