给定一个包含2列的表格JobTable
:JobId
,JobStatus
(还有其他但我将其排除,因为它们对问题没有影响)。< / p>
一个进程, WorkGenerator , INSERTs 行进入表中。
另一个进程 Worker 执行名为GetNextJob
的存储过程。
现在,GetNextJob
执行 SELECT 以查找下一项工作( JobStatus = 1 ),然后执行更新将该作品标记为正在进行中( JobStatus = 2 )。
我们希望通过多个工作人员流程进行扩展,但发现多个工作人员可以选择相同的工作。
我有以下疑问:
GetNextJob
中,我可以合并吗?
SELECT和UPDATE成单个查询
并使用OUTPUT子句来获取
的JobId?我很欣赏有效的答案,但也解释了为什么它们有效。
答案 0 :(得分:2)
答案 1 :(得分:2)
让我们建立一个解决方案:
确保UPDATE检查@@ ROWCOUNT
在UPDATE
之后检查@@ROWCOUNT以确定哪个工作人员进程获胜。
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
请注意,使用上述过程,未获胜的进程不会返回任何行,需要再次调用该过程以获取下一行。
上述将解决大多数情况,其中两个工作人员都选择同一工作,因为UPDATE
防范了这一点。但是,对于同一个jobId,两个工人的@@ 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都是必需的。 SELECT上的UPDLOCK告诉MSSQL锁定行,就像它被更新一样,直到提交事务为止。 ROWLOCK(可能不是必需的)但告诉MSSQL只锁定SELECT返回的ROW。
优化锁定
当1进程使用ROWLOCK提示锁定行时,其他进程将被阻塞,等待释放该锁。可以指定READPAST提示。来自MSDN:
指定READPAST时,两者都有 行级和页级锁是 跳过。也就是数据库引擎 而是跳过行或页面 阻止当前交易 直到锁被释放。
这将阻止其他进程被阻止并提高性能。
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和更新
组合SELECT和UPDATE并使用SET获取ID。
例如:
DECLARE @val int
UPDATE JobTable
SET @val = JobId,
status = 2
WHERE rowid = (SELECT min(JobId) FROM JobTable WHERE status = 1)
SELECT @val
这仍然要求事务是SERIALIZABLE以确保每行只分配给一个Worker。
要考虑:再次合并SELECT和UPDATE
组合SELECT和UPDATE并使用Output子句。