C# 避免对同一数据库表进行并发读取时出现重复
避免对同一数据库表进行并发读取时出现重复 我们有一个包含任务列表的表C# 避免对同一数据库表进行并发读取时出现重复,c#,sql-server,entity-framework,concurrency,C#,Sql Server,Entity Framework,Concurrency,避免对同一数据库表进行并发读取时出现重复 我们有一个包含任务列表的表 Table RecordsTable RecordID RecordName ... ... IsProcessed 多台工作计算机从表中读取数据,一旦任务被处理,则将IsProcessed标记为true 因此,如果我们希望下面的代码在没有重复的情况下工作 C语言中的伪代码# 我们希望避免重复处理记录(因为ProcessRecords()包含邮件等)如果我们将上面的整个代码包装为
Table RecordsTable
RecordID
RecordName
...
...
IsProcessed
多台工作计算机从表中读取数据,一旦任务被处理,则将IsProcessed标记为true
因此,如果我们希望下面的代码在没有重复的情况下工作
C语言中的伪代码#
我们希望避免重复处理记录(因为ProcessRecords()包含邮件等)如果我们将上面的整个代码包装为
一个事务是否意味着来自两个不同工作人员的两个呼叫将导致不重复的记录
如果workerA首先对它得到的表发出调用
var recordSetWorkerA = objectontext.recordstable.Where(somecondition...).Where(c => c.IsProcessed == false).Take(10);
如果workerB在worker a已在事务中之后发出调用,则会因为尝试读取锁定行而导致以下语句无法执行吗
或者移动到下一个10条记录
var recordSetWorkerB = objectontext.recordstable.Where(somecondition...).Where(c => c.IsProcessed == false).Take(10);
有没有什么模式我们应该看看 一个选项是显式地使isProcessed成为{ready,processing,processed}的三态枚举。我不知道如何在ActiveRecord中执行此操作,但您需要一个SQL语句,如:
UPDATE RecordsTable
SET ProcessedState = 'processing'
WHERE RecordId = 1
AND ProcessedState = 'ready';
确保此语句只更新了一行。如果是零行,那么就有人抢先完成了这项任务。确保此语句在其自己的事务中以至少“read committed”隔离级别执行。仅将代码包装到事务中是不够的。当然,在保存更改时会出现异常,但为时已晚 您真正需要的是将记录标记为正在处理,而不仅仅是已完成的处理。我看到两种解决办法:
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)
}
foreach(记录集中的记录单记录)
{
//RecordsInProcess是一个全球可用的并发数据库,您的工作人员是孤立的。不同的机器可以在任何给定的时间点出现并开始处理记录。因此,我们使用选项2-我们需要做的更改不是查询前N个匹配记录,而是逐个检索和处理记录。这会导致di吗差异如果我们得到前N条记录,并且在一个事务标记内,所有记录都被标记为“正在处理”然后继续处理-因为在我们的例子中,处理可能需要很长时间-我们对外部web服务等进行了大量调用。不,不会,但您必须更仔细地观察您的事务。您可以使用单个语句获取任务并将其标记为您的任务。使用输出的更新de>子句可以在返回开始处理所需的数据时标记任务。简言之:检索任务需要存储过程,因为这需要手工编写SQL,以确保在原子操作中只有一个工作人员加载这些记录并将其切换到处理
状态(如回答中所建议的)。
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)
}