我在MS SQL Server 2008 R2中有一个简单的队列实现。这是队列的精髓:

CREATE TABLE ToBeProcessed 
(
    Id BIGINT IDENTITY(1,1) PRIMARY KEY NOT NULL,
    [Priority] INT DEFAULT(100) NOT NULL,
    IsBeingProcessed BIT default (0) NOT NULL,
    SomeData nvarchar(MAX) NOT null
)

我想原子选择按优先级和ID订购的顶部n行是错误的,并更新这些行以说它们正在处理。我以为我会使用更新,顶部,输出和订单的组合,但不幸的是,您无法在更新语句中使用顶部和订单。

因此,我已经制作了一个条款来限制更新,该子查询通过订单(见下文)。我的问题是,整个陈述是原子,还是我需要将其包裹在交易中?

DECLARE @numberToProcess INT = 2

CREATE TABLE #IdsToProcess
(
    Id BIGINT NOT null
)

UPDATE 
    ToBeProcessed
SET
    ToBeProcessed.IsBeingProcessed = 1
OUTPUT 
    INSERTED.Id 
INTO
    #IdsToProcess   
WHERE
    ToBeProcessed.Id IN 
    (
        SELECT TOP(@numberToProcess) 
            ToBeProcessed.Id 
        FROM 
            ToBeProcessed 
        WHERE
            ToBeProcessed.IsBeingProcessed = 0
        ORDER BY 
            ToBeProcessed.Id, 
            ToBeProcessed.Priority DESC)

SELECT 
    *
FROM 
    #IdsToProcess

DROP TABLE #IdsToProcess

这是一些插入一些虚拟行的SQL:

INSERT INTO ToBeProcessed (SomeData) VALUES (N'');
INSERT INTO ToBeProcessed (SomeData) VALUES (N'');
INSERT INTO ToBeProcessed (SomeData) VALUES (N'');
INSERT INTO ToBeProcessed (SomeData) VALUES (N'');
INSERT INTO ToBeProcessed (SomeData) VALUES (N'');
有帮助吗?

解决方案

如果我理解问题的动机,您想避免两项并发交易都可以执行子查询以使顶n行进行处理,然后继续更新相同的行?

在这种情况下,我会使用这种方法。

;WITH cte As
(
SELECT TOP(@numberToProcess) 
            *
        FROM 
            ToBeProcessed WITH(UPDLOCK,ROWLOCK,READPAST) 
        WHERE
            ToBeProcessed.IsBeingProcessed = 0
        ORDER BY 
            ToBeProcessed.Id, 
            ToBeProcessed.Priority DESC
)            
UPDATE 
    cte
SET
    IsBeingProcessed = 1
OUTPUT 
    INSERTED.Id 
INTO
    #IdsToProcess  

我早些时候有点不确定SQL Server是否会服用 U 使用子查询处理您的版本时锁定锁定,从而阻止了两项并发交易,从而读取相同的读取 TOP N 行。事实并非如此。

测试表

CREATE TABLE JobsToProcess
(
priority INT IDENTITY(1,1),
isprocessed BIT ,
number INT
)

INSERT INTO JobsToProcess
SELECT TOP (1000000) 0,0
FROM master..spt_values v1, master..spt_values v2

测试脚本(在2个并发SSMS会话中运行)

BEGIN TRY
DECLARE @FinishedMessage VARBINARY (128) = CAST('TestFinished' AS  VARBINARY (128))
DECLARE @SynchMessage VARBINARY (128) = CAST('TestSynchronising' AS  VARBINARY (128))
SET CONTEXT_INFO @SynchMessage

DECLARE @OtherSpid int

WHILE(@OtherSpid IS NULL)
SELECT @OtherSpid=spid 
FROM sys.sysprocesses 
WHERE context_info=@SynchMessage and spid<>@@SPID

SELECT @OtherSpid


DECLARE @increment INT = @@spid
DECLARE @number INT = @increment

WHILE (@number = @increment AND NOT EXISTS(SELECT * FROM sys.sysprocesses WHERE context_info=@FinishedMessage))
UPDATE JobsToProcess 
SET @number=number +=@increment,isprocessed=1
WHERE priority = (SELECT TOP 1 priority 
                   FROM JobsToProcess 
                   WHERE isprocessed=0 
                   ORDER BY priority DESC)

SELECT * 
FROM JobsToProcess 
WHERE number not in (0,@OtherSpid,@@spid)
SET CONTEXT_INFO @FinishedMessage
END TRY
BEGIN CATCH
SET CONTEXT_INFO @FinishedMessage
SELECT ERROR_MESSAGE(), ERROR_NUMBER()
END CATCH

几乎立即执行停止,因为两个并发交易都更新了同一行,因此 S 识别的锁 TOP 1 priority 必须在aquires a之前被释放 U 锁定,然后进行2次交易来获取行 UX 锁定序列。

Heap

如果添加了CI ALTER TABLE JobsToProcess ADD PRIMARY KEY CLUSTERED (priority) 然后,僵局几乎立即发生,就像在这种情况下一样 S 锁没有发布,一项交易aquires a U 锁定行,等待将其转换为 X 锁定,另一笔交易仍在等待转换 S 锁定到 U 锁。

Clustered Index

如果上面的查询已更改为使用 MIN 而不是 TOP

WHERE priority = (SELECT MIN(priority)
                   FROM JobsToProcess 
                   WHERE isprocessed=0 
                   )

然后SQL Server设法从计划中完全消除了子查询并采取 U 一直锁定。

enter image description here

其他提示

根据我的所有经验和我读过的所有文档,每个T-SQL的陈述都应该是原子。您拥有的单个T-SQL语句,Ergo应该是原子,并且不需要明确的事务语句。我多次使用过这种精确的逻辑,从来没有遇到过问题。我期待看到是否有人作为支持的替代意见。

顺便说一句,查看排名函数,特别是row_number(),以检索设定的项目数量。语法可能有点尴尬,但总体而言,它们是灵活而强大的工具。 (大约有一个少数数十亿个堆叠的问题和答案来讨论它们。)

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top