Dan*_*ars 7 t-sql sql-server sql-server-2008-r2
我在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
)
Run Code Online (Sandbox Code Playgroud)
我想原子地选择按优先级排序的前n行和IsBeingProcessed为false的id,并更新这些行以表示它们正在被处理.我以为我会使用Update,Top,Output和Order By的组合,但不幸的是你不能在Update语句中使用top和order by.
所以我创建了一个in子句来限制更新,并且子查询按顺序执行(见下文).我的问题是,这整个语句是原子的,还是我需要将它包装在一个事务中?
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
Run Code Online (Sandbox Code Playgroud)
这是一些插入一些虚拟行的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'');
Run Code Online (Sandbox Code Playgroud)
如果我理解问题的动机,你想要避免两个并发事务都可以执行子查询以获得前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
Run Code Online (Sandbox Code Playgroud)
之前我有点不确定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
Run Code Online (Sandbox Code Playgroud)
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
Run Code Online (Sandbox Code Playgroud)
几乎立即执行停止,因为两个并发事务都更新同一行,因此S在识别TOP 1 priority必须获取U锁定之前获取的锁定在获取锁定之前,然后2个事务继续获取行U并按X顺序锁定.

如果添加了CI,则ALTER TABLE JobsToProcess ADD PRIMARY KEY CLUSTERED (priority)几乎立即发生死锁,因为在这种情况下行S锁没有被释放,一个事务获取U行上的锁并等待将其转换为X锁,另一个事务仍在等待转换它S锁定U锁.

如果上面的查询被更改为使用MIN而不是TOP
WHERE priority = (SELECT MIN(priority)
FROM JobsToProcess
WHERE isprocessed=0
)
Run Code Online (Sandbox Code Playgroud)
然后SQL Server设法完全消除计划中的子查询并一直U锁定.

| 归档时间: |
|
| 查看次数: |
3006 次 |
| 最近记录: |