可能正在数据库队列中处理相同的消息(SQL Server)
我们有时会遇到作业队列中的消息被处理两次。在我们的系统环境中,有两个服务可以直接处理来自队列表T_JOB_的消息 下面您可以看到服务调用的存储过程,该服务假定从队列中拾取消息 您能看到是否有一些明显的错误使得两个服务可以从T_JOB_DIRECT表中选择相同的消息吗可能正在数据库队列中处理相同的消息(SQL Server),sql,sql-server,database,Sql,Sql Server,Database,我们有时会遇到作业队列中的消息被处理两次。在我们的系统环境中,有两个服务可以直接处理来自队列表T_JOB_的消息 下面您可以看到服务调用的存储过程,该服务假定从队列中拾取消息 您能看到是否有一些明显的错误使得两个服务可以从T_JOB_DIRECT表中选择相同的消息吗 CREATE PROCEDURE [dbo].[usp_GetOneBatchJobForExec] @sMachineName NVARCHAR(50) AS BEGIN DECLARE @JOB_DIRECT_
CREATE PROCEDURE [dbo].[usp_GetOneBatchJobForExec]
@sMachineName NVARCHAR(50)
AS
BEGIN
DECLARE @JOB_DIRECT_ID INTEGER
SET NOCOUNT ON
SET TRANSACTION ISOLATION LEVEL READ COMMITTED
IF RTRIM(ISNULL(@sMachineName, '')) = '' BEGIN
RAISERROR('@sMachineName parameter must be supplied!',16,1)
END
ELSE
BEGIN
SET @JOB_DIRECT_ID = 0
WHILE @JOB_DIRECT_ID IS NOT NULL
BEGIN
-- If there is an job that has been marked for this service then return that.
SET @JOB_DIRECT_ID = NULL
SELECT TOP 1 @JOB_DIRECT_ID = JOB_DIRECT_ID FROM dbo.T_JOB_DIRECT WHERE IN_PROCESS_BY = @sMachineName
IF @JOB_DIRECT_ID IS NULL
BEGIN
-- else take highest prioritized job that is free
SELECT TOP 1 @JOB_DIRECT_ID = JOB_DIRECT_ID
FROM dbo.T_JOB_TYPE AS JY
INNER JOIN dbo.T_JOB_TYPE_ON_INST AS JTI
ON JY.JOB_TYPE_ID = JTI.JOB_TYPE_ID
INNER JOIN dbo.T_JOB_DIRECT AS JD
ON JD.JOB_TYPE_ID = JTI.JOB_TYPE_ID
AND JD.INST_ID = JTI.INST_ID
WHERE JD.IN_PROCESS_BY IS NULL AND JD.START_DATETIME < GETDATE()
ORDER BY JD.START_DATETIME DESC, JTI.PRIORITY DESC
IF @JOB_DIRECT_ID IS NOT NULL
BEGIN
--Mark the job as taken and pick it up the next loop OR if update misses (someone else got it before) then get anotehr i next loop.
UPDATE dbo.T_JOB_DIRECT SET IN_PROCESS_BY = @sMachineName WHERE JOB_DIRECT_ID = @JOB_DIRECT_ID AND IN_PROCESS_BY IS NULL
END
END
ELSE
BEGIN
-- Return job information for the selected job.
SELECT TOP 1 JD.*, JY.JOB_NAME
FROM dbo.T_JOB_TYPE AS JY INNER JOIN dbo.T_JOB_TYPE_ON_INST AS JTI
ON JY.JOB_TYPE_ID = JTI.JOB_TYPE_ID
INNER JOIN dbo.T_JOB_DIRECT AS JD
ON JD.JOB_TYPE_ID = JTI.JOB_TYPE_ID
AND JD.INST_ID = JTI.INST_ID
WHERE JOB_DIRECT_ID = @JOB_DIRECT_ID
--Set ID NULL to exit the loop
SET @JOB_DIRECT_ID = NULL
END
END --LOOP
END --IF RAISE
END
它应该只选择一个具有相同ID的作业,如果在\u进程中,\u BY=NULL。只有当且仅当没有其他人在选择和更新之间选择作业时,才能选择作业
程序应该能够处理一场比赛,而他认为你没有一个有效的分数。
如果没有和IN_PROCESS_BY为空,我们肯定会遇到问题 看起来你有比赛条件。具体地说,从选择行到将其更新为“正在处理”之间有一段时间。您可以通过一些查询提示和在事务中包装select和update来解决这个问题。您的选择可以类似于:
IF @JOB_DIRECT_ID IS NULL
BEGIN
BEGIN TRANSACTION;
SELECT TOP 1 @JOB_DIRECT_ID = JOB_DIRECT_ID
FROM dbo.T_JOB_TYPE AS JY
INNER JOIN dbo.T_JOB_TYPE_ON_INST AS JTI
ON JY.JOB_TYPE_ID = JTI.JOB_TYPE_ID
INNER JOIN dbo.T_JOB_DIRECT AS JD with (updlock, rowlock, readpast)
ON JD.JOB_TYPE_ID = JTI.JOB_TYPE_ID
AND JD.INST_ID = JTI.INST_ID
WHERE JD.IN_PROCESS_BY IS NULL
AND JD.START_DATETIME < GETDATE()
ORDER BY JD.START_DATETIME DESC, JTI.PRIORITY DESC;
IF @JOB_DIRECT_ID IS NOT NULL
BEGIN
-- Mark the job as taken and pick it up the next loop
-- OR if update misses (someone else got it before)
-- then get another in next loop.
UPDATE dbo.T_JOB_DIRECT
SET IN_PROCESS_BY = @sMachineName
WHERE JOB_DIRECT_ID = @JOB_DIRECT_ID
AND IN_PROCESS_BY IS NULL
END
COMMIT TRANSACTION;
END
另一方面,我将在in_process_by列的t_job_direct表中添加一个唯一的筛选索引,如果您还没有索引,则该索引的值不为null。这将防止给定机器在任何时候处理多行。由于上面Ben Thul的评论,我更改了存储过程以利用OUTPUT子句。原始sp是在SQL Server中引入OUTPUT子句之前编写的 这是最终的新版本
CREATE PROCEDURE [dbo].[usp_GetOneBatchJobForExec]
@sMachineName NVARCHAR(50)
AS
BEGIN
SET NOCOUNT ON;
SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
with cte as (
SELECT TOP(1) JD.*, JY.JOB_NAME
FROM dbo.T_JOB_TYPE AS JY
INNER JOIN dbo.T_JOB_TYPE_ON_INST AS JTI
ON JY.JOB_TYPE_ID = JTI.JOB_TYPE_ID
INNER JOIN dbo.T_JOB_DIRECT AS JD WITH (ROWLOCK, READPAST)
ON JD.JOB_TYPE_ID = JTI.JOB_TYPE_ID
AND JD.INST_ID = JTI.INST_ID
WHERE (JD.IN_PROCESS_BY IS NULL OR JD.IN_PROCESS_BY = @sMachineName) AND JD.START_DATETIME < GETDATE()
ORDER BY JD.IN_PROCESS_BY DESC, JD.START_DATETIME DESC, JTI.PRIORITY DESC
)
UPDATE cte
SET IN_PROCESS_BY = @sMachineName
OUTPUT inserted.JOB_DIRECT_ID, inserted.JOB_TYPE_ID, inserted.INST_ID, inserted.JOB_QUEUE_ID, inserted.JOB_DATA, inserted.REG_DATETIME,
inserted.REG_USER_ID, inserted.JOB_TIME_ID, inserted.IN_PROCESS_BY, inserted.START_DATETIME, deleted.JOB_NAME
END
感谢您抽出时间调查此事!我从程序的原始创建者那里得到了一些反馈。请看下面的编辑在原来的职位以上!可能但问题是,您在一个单独的select中获得了@JOB\u DIRECT\u ID,因此两个或多个进程可以同时获取同一行。另一种管理方法是在更新中使用OUTPUT子句,同时跳过select all。感谢您向我介绍OUTPUT子句。帮助了我们很多。
CREATE PROCEDURE [dbo].[usp_GetOneBatchJobForExec]
@sMachineName NVARCHAR(50)
AS
BEGIN
SET NOCOUNT ON;
SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
with cte as (
SELECT TOP(1) JD.*, JY.JOB_NAME
FROM dbo.T_JOB_TYPE AS JY
INNER JOIN dbo.T_JOB_TYPE_ON_INST AS JTI
ON JY.JOB_TYPE_ID = JTI.JOB_TYPE_ID
INNER JOIN dbo.T_JOB_DIRECT AS JD WITH (ROWLOCK, READPAST)
ON JD.JOB_TYPE_ID = JTI.JOB_TYPE_ID
AND JD.INST_ID = JTI.INST_ID
WHERE (JD.IN_PROCESS_BY IS NULL OR JD.IN_PROCESS_BY = @sMachineName) AND JD.START_DATETIME < GETDATE()
ORDER BY JD.IN_PROCESS_BY DESC, JD.START_DATETIME DESC, JTI.PRIORITY DESC
)
UPDATE cte
SET IN_PROCESS_BY = @sMachineName
OUTPUT inserted.JOB_DIRECT_ID, inserted.JOB_TYPE_ID, inserted.INST_ID, inserted.JOB_QUEUE_ID, inserted.JOB_DATA, inserted.REG_DATETIME,
inserted.REG_USER_ID, inserted.JOB_TIME_ID, inserted.IN_PROCESS_BY, inserted.START_DATETIME, deleted.JOB_NAME
END