我有一个 EF 代码优先上下文,它表示处理应用程序可以检索和运行的作业队列。这些处理应用程序可以在不同的机器上运行,但指向同一个数据库。
上下文提供了一个方法,QueueItem
如果有任何工作要做,则返回 a 或 null,称为CollectQueueItem
。
为了确保没有两个应用程序可以获取相同的作业,收集发生在一个带有ISOLATION LEVEL
of的事务中REPEATABLE READ
。这意味着如果有两次尝试同时获取同一个作业,则将选择一个作为deadlock victim
并回滚。我们可以通过捕获DbUpdateException
和返回来处理这个问题null
。
这是该CollectQueueItem
方法的代码:
public QueueItem CollectQueueItem()
{
using (var transaction = new TransactionScope(TransactionScopeOption.Required, new TransactionOptions { IsolationLevel = IsolationLevel.RepeatableRead }))
{
try
{
var queueItem = this.QueueItems.FirstOrDefault(qi => !qi.IsLocked);
if (queueItem != null)
{
queueItem.DateCollected = DateTime.UtcNow;
queueItem.IsLocked = true;
this.SaveChanges();
transaction.Complete();
return queueItem;
}
}
catch (DbUpdateException) //we might have been the deadlock victim. No matter.
{ }
return null;
}
}
我在 LinqPad 中进行了测试,以检查它是否按预期工作。这是下面的测试:
var ids = Enumerable.Range(0, 8).AsParallel().SelectMany(i =>
Enumerable.Range(0, 100).Select(j => {
using (var context = new QueueContext())
{
var queueItem = context.CollectQueueItem();
return queueItem == null ? -1 : queueItem.OperationId;
}
})
);
var sw = Stopwatch.StartNew();
var results = ids.GroupBy(i => i).ToDictionary(g => g.Key, g => g.Count());
sw.Stop();
Console.WriteLine("Elapsed time: {0}", sw.Elapsed);
Console.WriteLine("Deadlocked: {0}", results.Where(r => r.Key == -1).Select(r => r.Value).SingleOrDefault());
Console.WriteLine("Duplicates: {0}", results.Count(r => r.Key > -1 && r.Value > 1));
//IsolationLevel = IsolationLevel.RepeatableRead:
//Elapsed time: 00:00:26.9198440
//Deadlocked: 634
//Duplicates: 0
//IsolationLevel = IsolationLevel.ReadUncommitted:
//Elapsed time: 00:00:00.8457558
//Deadlocked: 0
//Duplicates: 234
我跑了几次测试。如果没有REPEATABLE READ
隔离级别,相同的作业将由不同的 theads 检索(在 234 个重复项中可见)。使用REPEATABLE READ
,作业只检索一次,但性能会受到影响,并且有 634 个死锁事务。
我的问题是:有没有办法在 EF 中获得这种行为而不会出现死锁或冲突的风险?我知道在现实生活中竞争会减少,因为处理器不会不断地访问数据库,但是,有没有办法安全地做到这一点而不必处理 DbUpdateException?我可以在没有REPEATABLE READ
隔离级别的情况下获得更接近版本的性能吗?或者死锁实际上并没有那么糟糕,我可以放心地忽略异常,让处理器在几毫秒后重试,并接受如果不是所有事务都同时发生,性能会很好?
提前致谢!