从表返回的重复记录没有重复

JNK*_*JNK 8 sql-server duplication sql-server-2008-r2 isolation-level

我有一个存储过程,用于查询用于在我们的系统中分配工作的繁忙队列表。有问题的表在 WorkID 上有一个主键,没有重复项。

查询的简化版本是:

INSERT INTO #TempWorkIDs (WorkID)
SELECT
        W.WorkID

    FROM
        dbo.WorkTable W

    WHERE
        (@bool_param = 0 AND
        ((W.InProgress = 0
         AND ISNULL(W.UserID, -1) != @userid_param
         AND (@bool_filtered = 0
              OR W.TypeID IN (SELECT TypeID FROM #Types AS t)))
         OR 
         (@bool_param = 1
          AND W.InProgress = 1
          AND W.UserID != @userid_param)
        OR
        (@Auto_Param = 0
         AND W.UserID = @userid_param)))
         OR
         (@bool_param = 1 AND W.UserID = @userid_param)
    OPTION
        (RECOMPILE)
Run Code Online (Sandbox Code Playgroud)

#Types表在过程中较早地填充。

正如我所说,WorkTable很忙,有时在此查询运行时,我怀疑其中一条记录正在从一组过滤器移动到另一组过滤器WHERE。具体来说,当有人开始处理一个项目,并且W.InProgress从 0 到 1的更改时,就会发生这种情况。当我尝试向此查询插入的临时表添加主键时,会出现重复的键冲突。

我在出错时生成的查询计划中确认没有并行性,隔离级别为READ COMMITTED,源表中没有重复记录。您还可以看到这里没有JOINs 或其他方法来获得笛卡尔积。

这是匿名查询计划:

在此处输入图片说明

问题是,是什么导致了重复,我怎样才能让它停止?

我认为READ COMMITTED应该在这里工作,我需要锁定。InProgress当我查询时记录上的位发生变化时,我几乎肯定会发生欺骗。我知道这一点是因为该表存储了该更改的时间,并且它在我查询并收到错误的几毫秒内。

Joe*_*ish 9

有一些棘手的情况会导致同一行从索引中读取两次,即使在隔离级别也是如此READ COMMITTED

您的查询不符合分配顺序扫描的条件,因此存储引擎将按照聚集键的顺序从表中读取数据。

对于您的表,您将InProgress用作聚簇键的第一列。当您扫描整个表时,您可能会获得行或页锁定。如果您在扫描开始附近读取一行,释放对它的锁定,该行将更新,以便InProgress从 0 更改为 1,然后在不同的页面中再次读取该行,那么您可能会WorkID从查询中看到重复的值.

有很多解决方法。您可以插入到堆中并简单地删除重复值。您可以DISTINCT在查询中添加一个。您还可以启用行版本隔离级别,以提供数据库已提交状态的稳定视图,无论是在事务开始时(快照隔离),还是在语句开始时(读取已提交快照隔离))。

也许添加锁定提示或更改表的结构是合适的。对于一个相当有趣的解决方案(可能不适合生产),您可以尝试向后读取索引。这可以通过一个多余TOPORDER BY. 下面是一个非常简单的演示来说明这一点:

CREATE TABLE #WorkTable (
    InProgress TINYINT NOT NULL,
    WorkID INT NOT NULL
    , PRIMARY KEY (InProgress, WorkID)
);

INSERT INTO #WorkTable WITH (TABLOCK)
SELECT (RN - 1) / 5000, RN
FROM
(
    SELECT TOP (10000) ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) RN
    FROM master..spt_values t1
    CROSS JOIN master..spt_values t2
) t
OPTION (MAXDOP 1);
Run Code Online (Sandbox Code Playgroud)

以下查询具有 Ordered:false 属性,但它仍会按聚集键顺序读取数据:

SELECT WorkId
FROM #WorkTable;
Run Code Online (Sandbox Code Playgroud)

但是,以下查询将以反向聚集顺序读取数据:

SELECT TOP (9223372036854775807) WorkId
FROM #WorkTable
ORDER BY InProgress DESC, WorkId DESC;
Run Code Online (Sandbox Code Playgroud)

我们可以通过查看扫描属性来看到这一点:

向后扫描

对于您的表,这意味着如果更新一行InProgress以使其从 0 更改为 1,则它出现两次的可能性将大大降低。它可能根本不会出现,这可能是一个不同的问题。