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


8

我有一个存储过程,用于查询繁忙的队列表,该表用于在我们的系统中分配工作。有问题的表在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)

#Types表在该过程的前面填充。

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

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

这是匿名查询计划:

在此处输入图片说明

问题是,是什么原因导致重复,我该如何停止?

我认为READ COMMITTED应该在这里工作,我需要锁定。我几乎肯定,当InProgress查询时记录中的位发生更改时,就会发生重复。我知道这是因为表存储了更改的时间,并且它在我查询并得到错误的毫秒之内。

Answers:


9

有一些棘手的情况下,这会导致在同一行中从索引中被读取两次,连下READ COMMITTED隔离级别

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

对于表,您将其InProgress作为集群键的第一列。扫描表时,很可能会获得行锁或页面锁。如果您在扫描开始附近读到一行,请松开它的锁,然后将该行更新为InProgress从0变为1,然后在另一页中再次读取该行,然后可以WorkID从查询中看到重复的值。

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

添加锁定提示或更改表的结构也许是适当的。对于一个相当有趣的解决方案(可能不适合生产),您可以尝试向后读取索引。这可以与多余的TOP一起使用ORDER 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);

以下查询具有Ordered:false属性,但仍将以集群键顺序读取数据:

SELECT WorkId
FROM #WorkTable;

但是,以下查询将以相反的群集顺序读取数据:

SELECT TOP (9223372036854775807) WorkId
FROM #WorkTable
ORDER BY InProgress DESC, WorkId DESC;

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

向后扫描

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

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.