Sql server 选择查询在并发更新期间跳过记录
我有一个由N个线程并发处理的表Sql server 选择查询在并发更新期间跳过记录,sql-server,sql-server-2008,tsql,concurrency,locking,Sql Server,Sql Server 2008,Tsql,Concurrency,Locking,我有一个由N个线程并发处理的表 CREATE TABLE [dbo].[Jobs] ( [Id] BIGINT NOT NULL CONSTRAINT [PK_Jobs] PRIMARY KEY IDENTITY, [Data] VARBINARY(MAX) NOT NULL, [CreationTimestamp] DATETIME2(7) NOT NULL,
CREATE TABLE [dbo].[Jobs]
(
[Id] BIGINT NOT NULL CONSTRAINT [PK_Jobs] PRIMARY KEY IDENTITY,
[Data] VARBINARY(MAX) NOT NULL,
[CreationTimestamp] DATETIME2(7) NOT NULL,
[Type] INT NOT NULL,
[ModificationTimestamp] DATETIME2(7) NOT NULL,
[State] INT NOT NULL,
[RowVersion] ROWVERSION NOT NULL,
[Activity] INT NULL,
[Parent_Id] BIGINT NULL
)
GO
CREATE NONCLUSTERED INDEX [IX_Jobs_Type_State_RowVersion] ON [dbo].[Jobs]([Type], [State], [RowVersion] ASC) WHERE ([State] <> 100)
GO
CREATE NONCLUSTERED INDEX [IX_Jobs_Parent_Id_State] ON [dbo].[Jobs]([Parent_Id], [State] ASC)
GO
处理后,作业的状态可以再次更改为0(新)
,也可以设置为100(已完成)
作业具有层次结构,仅当所有子作业都完成时,父作业才会获得状态=100(已完成)
。
一些工作进程调用存储过程([dbo].[Jobs\u GetCountWithExcludedState]
和@ExcludedState=100
)返回未完成的作业数,当它返回0时,父作业的状态可以设置为100(已完成)
如果实际上您的过程Jobs\u GetCountWithExcludedState
返回的记录数为0,而实际上有已提交的记录与您的条件匹配,这将非常令人不安。这是一个非常简单的过程。因此有两种可能性:
- 由于SQL Server或数据出现问题,查询失败
腐败
- 实际上,在提交时没有符合条件的已提交的记录
程序正在运行
腐败不太可能,但可能是原因。您可以使用来检查是否存在腐败
最有可能的是,实际上没有具有与@ParentId
参数相等的父ID的提交的作业记录,并且在运行时未处于100状态
我强调已提交,因为这是交易将看到的
在你的问题中,你从来没有真正解释过如何在作业中设置Parent\u ID
。我的第一个想法是,可能您正在检查未处理的子作业,但它没有找到任何子作业,但另一个进程将其添加为另一个未完成作业的父作业ID。这有可能吗
我看到您添加了一个更新,以表明在添加子作业记录时,父记录和子记录的更新被包装在事务中。这很好,但不是我要问的问题。这是我正在考虑的一种可能性:
- 将为父级插入并提交作业记录
作业\u GetFirstByType
获取父作业
- 工作线程处理它并调用
Jobs\u UpdateStatus
并将其状态更新为100
- 有东西对作业调用
Jobs\u GetCountWithExcludedState
,并返回0
- 子作业已创建并附加到已完成的父作业记录。。。这使得它现在又不完整了
我不是说这就是正在发生的事情。。。我只是问是否有可能,你正在采取什么措施来防止它?例如,在上述问题更新中的代码中,您选择了一个ParentJob
,将子项附加到事务外部。可能是您正在选择父作业,然后在运行将子作业添加到父作业的事务之前完成了该作业?或者父作业的最后一个子作业已完成,因此工作线程检查并标记父作业已完成,但其他一些工作线程已选择该作业作为新子作业的父作业
有许多不同的情况可能导致您描述的症状。我认为问题在于您尚未与我们共享的一些代码中,特别是关于如何创建作业以及围绕调用jobs\u GetCountWithExcludedState
的代码。如果您能提供更多信息,我认为您将更有可能找到一个可用的答案,否则我们所能做的就是猜测代码中可能发生的所有我们看不到的事情。我建议查看客户端以及您如何处理每个线程的事务和连接生存期。因为所有命令都在客户端事务上运行。您的问题几乎肯定是由您选择的“读取已提交”隔离级别引起的。此操作的行为取决于您对READ_COMMITTED_SNAPSHOT的配置设置,但无论哪种方式,它都允许另一个事务线程在SELECT和更新之间修改SELECT可能会看到的记录,因此您存在争用条件
用隔离级别“SERIALIZABLE”再试一次,看看这是否能解决您的问题。有关隔离级别的更多信息,文档非常有用:
您的sql代码看起来不错。因此,问题在于如何使用它
假设#0
调用过程“Jobs_GetCountWithExcludedState”时使用了完全错误的ID。因为是的,有时问题实际上只是一个小错误。然而,我怀疑这是你的情况
假设#1
检查字段“Activity=30”的代码是在“readuncommitted”隔离级别中执行的。然后,它将调用“Jobs_GetCountWithExcludedState”,其parentID可能尚未真正准备就绪,因为插入事务可能尚未结束或已回滚
假设#2
调用过程“Jobs_GetCountWithExcludedState”时使用的id不再具有子级。发生这种情况的原因可能有很多。
比如说,
- 插入子作业的事务因任何原因失败,但仍调用了此过程
- 一个子作业已删除,即将被替换
- 等
假设#3
在子作业获得其parentId分配之前,调用过程“Jobs_GetCountWithExcludedState”
结论
如您所见,我们需要关于两件事的更多信息:
1.如何调用“Jobs_GetCountWithExcludedState”。
2.如何插入作业。parentId是在插入时分配的还是稍晚更新的?它们是批量插入的吗?有没有附加代码来做其他事情
这也是我推荐y的地方
CREATE PROCEDURE [dbo].[Jobs_GetFirstByType]
@Type INT,
@CurrentState INT,
@NewState INT
AS
BEGIN
SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
DECLARE @JobId BIGINT;
BEGIN TRAN
SELECT TOP(1)
@JobId = Id
FROM [dbo].[Jobs] WITH (UPDLOCK, READPAST)
WHERE [Type] = @Type AND [State] = @CurrentState
ORDER BY [RowVersion];
UPDATE [dbo].[Jobs]
SET [State] = @NewState,
[ModificationTimestamp] = SYSUTCDATETIME()
OUTPUT INSERTED.[Id]
,INSERTED.[RowVersion]
,INSERTED.[Data]
,INSERTED.[Type]
,INSERTED.[State]
,INSERTED.[Activity]
WHERE [Id] = @JobId;
COMMIT TRAN
END
CREATE PROCEDURE [dbo].[Jobs_UpdateStatus]
@Id BIGINT,
@State INT,
@Activity INT
AS
BEGIN
UPDATE j
SET j.[State] = @State,
j.[Activity] = @Activity,
j.[ModificationTimestamp] = SYSUTCDATETIME()
OUTPUT INSERTED.[Id], INSERTED.[RowVersion]
FROM [dbo].[Jobs] j
WHERE j.[Id] = @Id;
END
CREATE PROCEDURE [dbo].[Jobs_GetCountWithExcludedState]
@ParentId INT,
@ExcludedState INT
AS
BEGIN
SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
SELECT COUNT(1)
FROM [dbo].[Jobs]
WHERE [Parent_Id] = @ParentId
AND [State] <> @ExcludedState
END
using (var ts = new TransactionScope())
{
_jobManager.AddChilds(parentJob);
parentJob.State = 0;
parentJob.Activity = 30; // in this activity worker starts checking child jobs
ts.Complete();
}