sql-server – 在并发更新期间选择查询跳过记录

前端之家收集整理的这篇文章主要介绍了sql-server – 在并发更新期间选择查询跳过记录前端之家小编觉得挺不错的,现在分享给大家,也给大家做个参考。
我有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,[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(新)的表 – 它可以被这个状态的任何工作人员使用.当工作者获得此队列项时,状态更改为50(处理),作业变为不可用于其他用户(工作人员调用[dbo].[Jobs_GetFirstByType]与参数:Type = any,@ CurrentState = 0,@ NewState = 50).

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

处理完成后,作业状态可以再次更改为0(新建),也可以将其设置为100(已完成).

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

作业具有层次结构,只有当所有子项完成时,父作业才会获得State = 100(Completed).
一些工作人员调用存储过程([dbo].[Jobs_GetCountWithExcludedState] with @ ExcludedState = 100)返回未完成作业的数量,当它返回0时,父作业状态可以设置为100(已完成).

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

这个存储过程的主要问题是奇怪的行为.有时候,为父工作返回0,但它完全没有完成工作.我尝试打开更改数据跟踪和一些调试信息(包括分析) – 子作业100%没有状态= 100,当SP返回0.
看来,SP跳过不在100(已完成)状态的记录,但是为什么会发生,如何防止这种情况呢?

UPD:
调用[dbo].[Jobs_GetCountWithExcludedState]在父作业有子代时启动.当工作人员开始检查未存在的子作业时,由于创建子项并将其设置为包含在事务中的父作业检查活动,那么就不会有这种情况:

using (var ts = new TransactionScope())
{
    _jobManager.AddChilds(parentJob);

    parentJob.State = 0;
    parentJob.Activity = 30; // in this activity worker starts checking child jobs

    ts.Complete();
}

解决方法

如果实际上你的程序Jobs_GetCountWithExcludedState在事实上符合您的条件的提交记录时返回0个记录的数量将是非常令人不安的.这是一个非常简单的过程.所以有两种可能性:

>由于sql Server或数据的问题,查询失败
腐败.
>实际上没有符合条件的确定记录
程序运行.

腐败是不可能的,但可能的原因.您可以使用DBCC CHECKDB检查是否存在损坏.

最有可能的是,实际上没有任何一个Parent_ID等于@ParentId参数并且在运行时处于100状态的作业记录.

我强调承诺,因为这是交易将会看到的.

你从来没有真正解释你的问题如何在工作上设置Parent_ID.我的第一个想法是,也许你正在检查未处理的子作业,它没有找到,但是另一个进程将其添加为另一个不完整作业的Parent_ID.这是可能吗?

我看到你添加了一个更新,以显示当您添加一个子作业记录时,父和子记录的更新被包装在事务中.这是好的,但不是我问的问题.这是我正在考虑的一种可能性:

>为父母插入并提交作业记录.
> Jobs_GetFirstByType抓取父作业.
>工作线程处理它并调用Jobs_UpdateStatus并将其状态更新为100.
>某些东西调用Job_GetCountWithExcludedState作业并返回0.
>创建子作业并将其附加到已完成的父作业记录…,使其再次成为不完整的.

我不是说这是发生了什么…我只是问是否可能,并采取什么步骤来防止它?例如,在您的问题更新中的上述代码中,您选择一个ParentJob将该子项附加到事务外部.可能是您正在选择父作业,然后在运行将子添加到父项的事务之前完成它?或者也可能是父作业的最后一个子作业完成,以便工作线程检查并标记父项完成,但是一些其他工作线程已经选择作为新的子作业的父级作业?

有许多不同的情况可能会导致你所描述的症状.我相信这个问题是在你没有与我们分享代码中找到的,特别是关于如何创建作业以及调用Jobs_GetCountWithExcludedState的代码.如果你能给出更多的信息,我想你会更有可能找到一个可用的答案,否则我们可以做的最好的事情是猜测在代码中可能发生的所有事情,我们看不到.

原文链接:https://www.f2er.com/mssql/82214.html

猜你在找的MsSQL相关文章