我想在Sql Server 2012中实现有序作业队列。以下是上下文:
请查看以下SQL小提琴 - http://sqlfiddle.com/#!6/ca1a2f/1
理想情况下,我会使用带有UPDATE TOP (N)
提示和READPAST
子句的单个OUTPUT
语句。但是,UPDATE
不承诺任何订单,因此我采用了不同的方法:
SELECT TOP (N)
和UPDLOCK
提示将READPAST
排序到临时表中。实际上,我保留了这些记录。UPDATE
子句的常规OUTPUT
。但是,这样的批量更新可能会失败,因为批量中的作业是并发限制的。在这种情况下,我回过头来迭代保留的记录并尝试将它们标记为逐个运行,默默地跳过那些不符合并发限制的记录。
以下是SQL Fiddle的完整查询:
BEGIN TRAN
exec sp_executesql N'
DECLARE @Running TABLE (WorkItemId BIGINT)
SELECT TOP (@Count) WorkItemId INTO #Candidates
FROM BackgroundJobWork WITH (UPDLOCK, READPAST)
WHERE Status = 0
ORDER BY WorkItemId
BEGIN TRY
PRINT '' *** BATCH *** ''
UPDATE BackgroundJobWork SET Status = 3
OUTPUT inserted.WorkItemId INTO @Running
FROM BackgroundJobWork
WHERE WorkItemId IN (SELECT WorkItemId FROM #Candidates)
END TRY
BEGIN CATCH
PRINT '' *** ONE BY ONE *** ''
DECLARE @WorkItemId BIGINT
DECLARE c CURSOR FAST_FORWARD FOR
SELECT WorkItemId FROM #Candidates ORDER BY WorkItemId
OPEN c
FETCH NEXT FROM c INTO @WorkItemId
WHILE @@FETCH_STATUS = 0
BEGIN
BEGIN TRY
UPDATE BackgroundJobWork SET Status = 3
OUTPUT inserted.WorkItemId INTO @Running
FROM BackgroundJobWork
WHERE WorkItemId = @WorkItemId
END TRY
BEGIN CATCH
END CATCH
FETCH NEXT FROM c INTO @WorkItemId
END
CLOSE c
DEALLOCATE c
END CATCH
SELECT * FROM @Running
',N'@Count int',@Count=6
ROLLBACK
(回滚用于测试目的,状态0表示收到,状态3表示正在运行)
所以,有两种情况:
我的目标是测试同时运行此查询的两个代理不会相互干扰,即没有人会被锁定等待另一个完成。
我首先运行以下查询来模拟并发限制的存在:
UPDATE BackgroundJobWork
SET ConcurrencyGroupName = CONVERT(NVARCHAR(2), CASE
WHEN WorkItemId % 2 = 0 THEN NULL
ELSE WorkItemId % 4
END) WHERE Status < 100
这产生以下结果:
SELECT WorkItemId,Status,ConcurrencyGroupName FROM BackgroundJobWork
WHERE Status < 100 ORDER BY WorkItemId
WorkItemId Status ConcurrencyGroupName
1 0 1
2 0 NULL
3 0 3
4 0 NULL
5 0 1
6 0 NULL
7 0 3
8 0 NULL
9 0 1
10 0 NULL
11 0 3
12 0 NULL
如你所见:
UPDATE BackgroundJobWork SET ConcurrencyGroupName = NULL WHERE Status < 100
删除所有并发限制。
不幸的是,我不知道如何在SQL Fiddle中演示两个代理。以下是我在SSMS中的表现:
ROLLBACK
声明。ROLLBACK
语句从第一个窗口回滚事务。批量更新工作得很好 - 第二个窗口没有被第一个窗口中启动的打开事务锁定。我可以在第一个窗口看到作业1,2,3,4,5,6,在第二个窗口看到7,8,9,10,11,12 -
但是,当我模拟并发限制(使用上述查询)时,第二个窗口被锁定,等待第一个窗口释放锁。
我对此感到非常困惑。毕竟,每个窗口仅更新先前使用相应SELECT
语句保留的记录!这些集合是不相交的 - UPDLOCK
和READPAST
保证它。
追加 - 查询持有的锁
我正在使用以下查询检查所持有的锁(在另一个SSMS窗口中):
DECLARE @locks TABLE (spid INT,
dbid INT,
ObjId BIGINT,
IndId INT,
Type NVARCHAR(10),
Resource NVARCHAR(128),
Mode NVARCHAR(10),
Status NVARCHAR(32))
INSERT INTO @locks EXECUTE sp_lock
SELECT spid, OBJECT_NAME(ObjId) ObjectName, i.name, l.Type, Mode, COUNT(1) Count
FROM @locks l
LEFT JOIN sys.indexes i ON i.index_id = l.IndId AND i.object_id = l.ObjId
WHERE Mode NOT IN ('S','IS') AND dbid = DB_ID('747_DFControl2')
GROUP BY spid,OBJECT_NAME(ObjId),l.Type,i.name,Mode
ORDER BY spid,OBJECT_NAME(ObjId),l.Type,i.name,Mode
(747_DFControl2是我的数据库的名称)
如果我在没有并发限制时运行它(即批量更新成功),我会得到以下输出:
spid ObjectName name Type Mode Count
60 BackgroundJobWork IX_Status KEY X 12
60 BackgroundJobWork PK_BackgroundJobWork KEY X 6
60 BackgroundJobWork IX_Status PAG IX 1
60 BackgroundJobWork PK_BackgroundJobWork PAG IX 1
60 BackgroundJobWork NULL TAB IX 1
spid 60对应于第一个窗口(具有打开事务的窗口)。我们没有看到第二个窗口 - 它已成功回滚。
这是启用并发限制并且第二个窗口(spid 63)等待释放锁的结果:
spid ObjectName name Type Mode Count
60 BackgroundJobWork IX_ConcurrencyRestriction KEY X 2
60 BackgroundJobWork IX_Status KEY X 12
60 BackgroundJobWork PK_BackgroundJobWork KEY X 6
60 BackgroundJobWork IX_ConcurrencyRestriction PAG IX 1
60 BackgroundJobWork IX_Status PAG IX 1
60 BackgroundJobWork PK_BackgroundJobWork PAG IX 1
60 BackgroundJobWork NULL TAB IX 1
63 BackgroundJobWork IX_ConcurrencyRestriction KEY X 1
63 BackgroundJobWork IX_Status KEY X 12
63 BackgroundJobWork PK_BackgroundJobWork KEY X 6
63 BackgroundJobWork IX_ConcurrencyRestriction PAG IX 1
63 BackgroundJobWork IX_Status PAG IX 1
63 BackgroundJobWork PK_BackgroundJobWork PAG IX 1
63 BackgroundJobWork NULL TAB IX 1
这并没有告诉我太多。
有人可以向我解释为什么第二个查询实例被锁定了吗?
修改
从问题不清楚,当我打开并发限制时,为什么批量更新会失败。很明显,从SQL Fiddle开始,BackgroundJobWork表上有一个条件唯一索引:
CREATE UNIQUE NONCLUSTERED INDEX IX_ConcurrencyRestriction ON BackgroundJobWork (ConcurrencyGroupName)
WHERE (Status=3 AND ConcurrencyGroupName IS NOT NULL)