使用LINQ to SQL,如何在多个进程之间划分队列表中的记录?

时间:2012-11-09 12:57:17

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

我使用SQL Server数据库表作为工作队列。

我有一位作家和多位读者。

编写器为队列表生成新记录(INSERT)。

每个阅读器查找要使用的特定状态(SELECT)的记录,获取一批记录,将每个记录标记为已拥有(UPDATE),处理它们,然后将所有已处理的记录标记为完成或失败(UPDATE)。记录的工作流程很简单:状态从“A”表示“已添加”,“B”表示“正在处理”,“C”表示“完成”或“F”表示“失败”。每个记录的所有者从状态“A”中的未设置变为唯一标识符,用于标识读取器的剩余步骤。唯一标识符(十进制)加上状态(nchar(1))加上标识作者较大批次的附加批处理ID(int)是处理中的三个关键字段。此外,该表还有一个LINQ用于并发检查的时间戳字段。

由于两位读者选择要处理的相同记录,我需要防止锁定延迟和更新失败。作为处理工作的一部分,进行Web服务调用可能需要不确定的时间来完成。我们通过交易向Web服务供应商付款,因此我不想进行调用,然后找出处理相同记录的另一个进程。这样的错误将花费我们数千美元。

我读过这篇文章:

Processing Data Queues in SQL Server with READPAST and UPDLOCK

看起来很有希望,但我决定使用LINQ to SQL。我读了这篇文章:

Linq to SQL with UPDLOCK

后者说没有办法告诉LINQ使用UPDLOCK而不是粗略地包装SQL过程。这是因为LINQ使用乐观并发,并且这些功能假设是悲观锁定。

那么如何使用LINQ支持的功能解决这个问题呢?我的应用程序是多线程的,但我可以有多个实例针对同一个队列表运行,所以我不能让一个调度程序将记录交给每个消费者线程。

我正在使用.NET 4.0。我的应用程序是一个普通的Windows控制台应用程序该解决方案应与SQL Server 2005和SQL Server 2008 R2一起使用。我不想使用消息传递系统。

更新:对SO的进一步研究。找到这篇文章:

LINQ to SQL - Queue

答案建议有一个调度员线程,我已经排除了。

更新2:找到关于SO的另一篇文章:

Atomically Mark and return a group of rows in database

这看起来很有希望。我需要执行直接SQL,但是一旦我记录了我的记录并获得了我的ID,其余的就可以在L2S中进行。

2 个答案:

答案 0 :(得分:0)

也许你可以对你的读者中的出列过程采取更悲观的态度。

让读者收到下一个排队的项目。让读者尝试将该项目出列。读者可以为出队呼叫中的项目分配唯一值,例如guid。

然后,读者可以尝试获取已排队的排队项目。当它收到该项目时,它可以比较它已分配的项目的guid。如果它们是相同的,那么读者就可以进行处理。

假设您的读者可以一次出列一个项目,这应该是可行的。我不确定每个读者出列多个项目时该怎么办。

答案 1 :(得分:0)

服务这是我的答案。我将存储过程与我的DataContext相关联以保留地址。它可以使用悲观并发在单个操作中选择,标记和返回记录。

CREATE PROCEDURE dbo.kccsp_ReserveAddresses
      @BATCH_SIZE int,
      @BATCH_ID int,
      @PROCESS_TOKEN numeric(18,0)
  AS
      -- Reserve a group of addresses in the queue so that a consumer may process them,
      -- but all other consumers will leave them alone.
      -- The query hints are essential to prevent lock contention, 
      -- or concurrency errors from multiple processors handling the same address.
      update TOP (@BATCH_SIZE) 
          KCC_GeoCodingAddressQueue WITH (ROWLOCK, READPAST, UPDLOCK)
      set 
          [Process Status] = 'B', 
          [Process Token] = @PROCESS_TOKEN
      output INSERTED.*
      where [Process Status] = 'A' and [Process Token] = 0

我是通过将过程拖到我的dbml设计器的结果表中来完成的。这会导致结果集与我的实体类相同,并在标记它们的同时返回我指定的所有记录。 SQL提示处理并发和锁争用。

当我需要在C#中调用它时,我这样做:

   int? batchSize = 50;
   int? batchIdToReserve = Batch.GeoBatchID;
   decimal? processorId = someId;
   var addresses = Context.kccsp_ReserveAddresses(batchSize, batchIdToReserve, processorId);