2012-07-28 124 views
1

并发避免重复读取过同一个数据库表并发避免重复读取过同一个数据库表

我们有一个包含任务列表

Table RecordsTable 
    RecordID 
    RecordName 
    ... 
    ... 
    IsProcessed 

多个工作机器读出的表表,一旦任务处理完成,标记IsProcessed为真。

因此,如果我们希望下面的代码没有重复工作,在C#

//get first 10 records that are not processed based on some other conditions 
var recordSet = objectontext.recordstable.Where(...).Where(c => c.IsProcessed == false).Take(10); 
//loop through the recordset in a transaction 
foreach(record singleRecord in recordSet) 
{ 
    bool result = ProcessRecord(); 
    //Mark isProcessed as true 
    if(result) 
     singleRecord.IsProcessed = true; 
    objectContext.Savechanges(); 
} 

我们希望避免的记录重复的处理(因为ProcessRecords()包含邮寄和这样)。 如果我们将上面的整个代码换成 ,则事务是否意味着来自两个不同工作人员的两个调用会导致非重复记录?

如果workerA首先发出调用它得到的表,

var recordSetWorkerA = objectontext.recordstable.Where(somecondition...).Where(c => c.IsProcessed == false).Take(10); 

如果workerB发出呼叫之后工作者来说已经是该交易将以下statment无法执行,因为试图读取锁定行 或移动到下一个10条记录?

var recordSetWorkerB = objectontext.recordstable.Where(somecondition...).Where(c => c.IsProcessed == false).Take(10); 

有没有我们应该看的任何模式。

+1

简而言之:您将需要用于检索任务的存储过程,因为这需要手写SQL来确保只有一个工作人员将加载这些记录并将其切换到原子操作中的“处理”状态(如答案中所述) 。 – 2012-07-29 16:30:18

回答

1

只是把你的代码包装到事务中是不够的。你当然会在SaveChanges上得到例外,但是太迟了。

你真正需要的是标记录为处理什么,而不仅仅是完成处理。我看到两个解决方案:

  1. 如果工人共享相同的状态(这意味着他们是在一个AppDomain中的线程,而不是几个并发工作服务),您可以使用ConcurrentDictionary来标记你正在被处理的记录。

    foreach(record singleRecord in recordSet) 
    { 
        //RecordsInProcess is a globally-available ConcurrentDictionary<recordIdType, record 
        if (!RecordsInProcess.TryAdd(singleRecord.RecordId, singleRecord)) 
         continue; //TryAdd will return false if such an element already exists 
    
        bool result = ProcessRecord(); 
        //Mark isProcessed as true 
        if(result) 
         singleRecord.IsProcessed = true; 
        objectContext.Savechanges(); 
        record junk; // we don't need it 
        RecordsInProcess.TryRemove(singleRecordId, out junk) 
    } 
    
  2. 如果工人被隔离,或者你只是想要一些更强大的,那么你必须标记的记录作为处理数据库,并使用过滤这些信息。这就是你必须使用交易的地方,并且非常小心地使用它们,因为它很容易陷入僵局。从并发角度来看,最有效率的做法是始终只从数据库中取一个未处理的记录,在做任何事之前将其标记为processing,然后继续处理。
+0

我们的工人是孤立的。不同的机器可以在任何时间点出现并开始处理记录。所以我们选择2 - 同样,我们需要做的变化不是查询前N个匹配记录,而是逐个检索和处理记录。如果我们得到最高的N条记录,并且在交易标记中所有这些记录都是“处理”,然后继续处理,那么它会产生什么影响?因为在我们的情况下,处理可能需要很长时间 - 我们做了很多呼叫外部网络服务等。 – user275157 2012-07-28 13:23:08

+0

不,它不会,但你必须更密切地关注你的交易。 – 2012-07-28 15:00:56

+2

您可以使用单个语句来获取任务并将其标记为您的任务。带'OUTPUT'子句的'UPDATE'可以标记任务,同时返回开始处理所需的数据。 – HABO 2012-07-28 15:36:59

1

一种选择是明确地使isProcessed {就绪,处理,处理}的三态枚举。我不知道如何在ActiveRecord的做到这一点,但你要像一个SQL语句:

UPDATE RecordsTable 
SET ProcessedState = 'processing' 
WHERE RecordId = 1 
    AND ProcessedState = 'ready'; 

确保只有一行被这个声明进行更新。如果行数为零,有人会让你胜任这项任务。确保此语句在其自己的事务中至少执行“读取已提交”隔离级别。