【问题标题】:T-SQL query returns same result to multiple users when results should be unique当结果应该是唯一的时,T-SQL 查询向多个用户返回相同的结果
【发布时间】:2018-02-12 23:07:06
【问题描述】:

我有一个 T-SQL 查询,它将工作队列中的下一个项目 ID 返回给任务工作者。在返回之前,查询会使用 workerID 更新行,因此不应多次分配它。此查询在大多数情况下都有效,但最近我们发现相同的唯一项目 ID 正在返回给多个任务工作者。

一些可能有帮助的环境信息:总体数量少于 100 个任务工作者。任务获取很少比每 5 秒更频繁,每个工作单元平均 10 秒,因此同一个工人每分钟进行 6-10 次获取。该表有大约 100 万条记录,整个查询的执行时间不到 1 秒。表 PK 是带有聚集索引的 itemID。

我无法在生产环境中运行它的用户之外复制该问题,因此我怀疑它与运行时相关。这给我留下了几个问题:

在 C 中的更新将其从 A 中的查询中排除之前,几乎同时启动的多个查询是否可能会产生相同的结果?

如果是,可以做些什么呢?如果没有,我接下来应该看哪里?

下面是查询的摘要。 A 和 B 看起来多余,但这是我支持的遗留代码。在实施更改之前我需要一个计划。

/*
Definitions
@workerID INT -- the task worker
,@workQueueID INT -- different work queues
,@itemID INT -- the unique task item
*/

/* Temp table to store next item */
DECLARE @temp TABLE(
        itemID INT,
        workQueueID INT,
        rownum INT
    )

/* A) Select row 1 into @temp */
INSERT INTO @temp
SELECT *
FROM
(
    SELECT  A.itemID,
        A.workQueueID,
        ROW_NUMBER() 
            OVER(
                PARTITION BY a.workQueueID
                ORDER BY    a.taskPriority DESC
            ) 'RowNum'
    FROM workQueue a
    WHERE workerID IS NULL
    AND workQueueID = @workQueueID
)
WHERE RowNum = 1

/* B) Set @itemID and @workQueueID */
SELECT TOP 1 @itemID = Q.itemID,
        @workQueueID = Q.workQueueID
FROM @temp T
JOIN workQueue Q
ON Q.itemID = T.itemID
WHERE Q.workerID IS NULL
ORDER BY Q.workQueueID

/* C) Assign workerID */
UPDATE workQueue
SET workerID = @workerID, 
    StartTime = GETDATE()
WHERE itemID = @itemID

/* D) Return xml string to task worker */
SELECT  @itemID [itemID]
FOR XML AUTO, ELEMENTS, TYPE

【问题讨论】:

  • 包装代码的事务在哪里,它使用什么隔离级别?如果没有事务,您应该期望多个用户以有趣的方式进行交互。提示:使用适当的软件(MySQL、Oracle、DB2 等)和版本标记数据库问题很有帮助,例如sql-server-2014。语法和功能的差异通常会影响答案。请注意,tsql 缩小了选择范围,但没有指定数据库。
  • 谢谢 Habo,我用 SQL Server 2014 更新了标签

标签: sql-server tsql sql-server-2014


【解决方案1】:

A) 一种解决方案是将所有内容包装在事务中,并在您第一次访问 workQueue 表时使用 UPDLOCK 提示。

B) 另一种解决方案是在您选择项目的同一语句中进行更新,例如:

/*
CREATE TABLE workQueue (
    workQueueID INT NOT NULL,
    itemID INT UNIQUE,
    workerID INT NULL,
    StartTime DATETIME NULL,
    taskPriority INT NOT NULL
)

CREATE INDEX IX_workQueue ON dbo.workQueue (taskPriority DESC) WHERE workerID IS NULL

INSERT INTO workQueue VALUES (1,1,NULL,NULL,2)
INSERT INTO workQueue VALUES (1,2,NULL,NULL,1)
INSERT INTO workQueue VALUES (2,3,NULL,NULL,4)
INSERT INTO workQueue VALUES (2,4,NULL,NULL,3)
*/

DECLARE @workerID INT=7 -- the task worker
,@workQueueID INT=1 -- different work queues
,@itemID INT -- the unique task item

UPDATE x
SET workerID = @workerID, StartTime = GETDATE(),
    @itemID=itemID, 
FROM (
    SELECT TOP 1 A.*
    FROM workQueue a
    WHERE workerID IS NULL
    AND workQueueID=@workQueueID
    ORDER BY a.taskPriority DESC
) x

SELECT  @itemID [itemID]
FOR XML PATH, ELEMENTS, TYPE

我不清楚工人如何与不同的工作队列相关:任何工人都可以从任何工作队列中获取项目?如果每个工作队列中有一个项目(但具有不同的优先级),是否有任何规则选择哪个项目?由于您的代码中有PARTITION BY,似乎没有规则(可以选择任何项目)。

【讨论】:

  • 感谢您对更新声明的建议。我将不得不尝试更多。我更新了帖子,将workQueueID = @workQueueID 包含在PARTITION BY 的where 子句中。工人确实分配了队列。
  • 如果@workQueueID 是输入参数,在您的问题的查询A) 中使用,那么在查询B) 中不需要它。无论如何,我更新了我的答案以将其视为输入参数。
猜你喜欢
  • 2012-05-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-12-22
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多