我正在编写一个后台服务,需要处理一系列作业,作为记录存储在sqlserver表中。该服务需要找到需要工作的最早的20个作业(where status = 'new'
),标记它们(set status = 'processing'
),运行它们,然后更新作业。
这是我需要帮助的第一部分。可能有多个线程同时访问数据库,我想确保“mark& return”查询以原子方式运行,或几乎以原样运行。
这项服务将花费相对较少的时间访问数据库,如果一项工作运行两次,它不是世界末日,所以我可能能够接受一小部分工作的概率,以增加简单性代码。
最好的方法是什么?我正在为我的数据层使用linq-to-sql,但我认为我必须为此调入t-sql。
答案 0 :(得分:10)
您的工作表是一个队列。编写用户表备份队列是一个众所周知的错误,因为它会导致死锁和可靠性问题。
最简单的方法是删除用户表并使用真正的queue代替。这将为您提供系统测试和验证的代码库上的死锁免费concurency免费队列。问题是队列周围的整个范例从INSERT和DELETE / UPDATE变为SEND / RECEIVE。另一方面,通过内置队列,您可以获得一些非常强大的免费赠品,即Activation和correlated items locking。
如果要继续沿着用户表支持的队列的路径继续,那么编写用户表队列的第二最重要的技巧是使用UPDATE ... OUTPUT:
WITH cte AS (
SELECT TOP(20) status, id, ...
FROM table WITH (ROWLOCK, READPAST, UPDLOCK)
WHERE status = 'new'
ORDER BY enqueue_time)
UPDATE cte
SET status = 'processing'
OUTPUT
INSERTED.id, ...
CTE语法只是为了方便正确放置TOP和ORDER BY,查询可以像使用派生表一样编写。你不能直接使用UPDATE ... TOP因为UPDATE不支持ORDER BY,你需要这个来满足你需求的“最老”部分。需要锁提示以促进并行处理线程之间的高度可靠性。
我说这是第二个最重要的技巧。最重要的是如何组织表格。对于队列,必须按(status, enqueue_time)
进行群集。如果你没有正确组织表,你最终会陷入死锁。先发制人的评论:在这种情况下,碎片化是无关紧要的。
答案 1 :(得分:8)
请在此处查看我的答案:SQL Server Process Queue Race Condition,它还可以一次管理20行。
基本上,在SQL Server中使用提示ROWLOCK,READPAST和UPDLOCK管理并发和轮询非常简单。
我不能对Linq发表评论,但是一个事务仍然让你对并发问题持开放态度:你需要使用我提到的提示
答案 2 :(得分:4)
建立在gbn's answer ...
如果您使用的是SQL Server 2005或更高版本,则可以使用UPDATE
语句中的OUTPUT
clause以原子方式返回更新的行:
UPDATE TOP (20) your_table
SET status = 'processing'
OUTPUT INSERTED.*
FROM your_table WITH (ROWLOCK, READPAST, UPDLOCK)
WHERE status = 'new'
答案 3 :(得分:1)
我知道这不是主题,但为此您可以使用MSMQ。消息队列将按顺序放置您的作业,并且它的线程安全。您也可以分配优先级MSMQ管理自己。您可以使用read或peek从队列中删除消息或只看到那里的消息。您可以使用命令设计模式来帮助您完成此任务。
答案 4 :(得分:0)
这不仅仅是在事务中运行T-SQL那么简单,还是我错过了什么?