在数据库中以原子方式标记并返回一组行

时间:2009-11-30 17:18:22

标签: sql sql-server linq-to-sql concurrency

我正在编写一个后台服务,需要处理一系列作业,作为记录存储在sqlserver表中。该服务需要找到需要工作的最早的20个作业(where status = 'new'),标记它们(set status = 'processing'),运行它们,然后更新作业。

这是我需要帮助的第一部分。可能有多个线程同时访问数据库,我想确保“mark& return”查询以原子方式运行,或几乎以原样运行。

这项服务将花费相对较少的时间访问数据库,如果一项工作运行两次,它不是世界末日,所以我可能能够接受一小部分工作的概率,以增加简单性代码。

最好的方法是什么?我正在为我的数据层使用linq-to-sql,但我认为我必须为此调入t-sql。

5 个答案:

答案 0 :(得分:10)

您的工作表是一个队列。编写用户表备份队列是一个众所周知的错误,因为它会导致死锁和可靠性问题。

最简单的方法是删除用户表并使用真正的queue代替。这将为您提供系统测试和验证的代码库上的死锁免费concurency免费队列。问题是队列周围的整个范例从INSERT和DELETE / UPDATE变为SEND / RECEIVE。另一方面,通过内置队列,您可以获得一些非常强大的免费赠品,即Activationcorrelated 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那么简单,还是我错过了什么?