在数据库上执行“原子”操作“IncreaseIf”。

5

我需要执行原子操作检查某个实体框架模型字段的值,如果其值为0,则将其增加。

我考虑过事务,例如:

bool controlPassed = false;
using (TransactionScope scope = new TransactionScope())
{
    var model = ...ModelEntities.first_or_default(...)
    if (model.field == 0){
        ++model.field;
        ...saveChanges();
        controlPassed = true;
    }
    scope.Complete();
}
if (controlPassed)
{
    ...
    using (TransactionScope scope = new TransactionScope())
    {
        --model.field;
        ...saveChanges();
        scope.Complete();
    }
}

当然,所有的try catch都可以使用。

我的问题是:它将如何工作?

这真的很难检查。 我有多线程应用程序。

是否有可能,两个或多个线程会通过控制(检查field == 0并增加它)?

谁会在数据库中被阻止(数据库、表、行、字段)?

我不能让两个或多个线程同时处于controlPassed段中。

1个回答

10

是否有可能,两个或多个线程会通过控制(检查该字段== 0并增加它)?

您拥有可序列化的事务(这是TransactionScope的默认值)。这意味着可能会有两个字段为0的线程,但在此之后立即发生死锁,因为第一个线程的事务持有字段的共享锁,而第二个线程的事务则持有同一字段的另一个共享锁。这些事务都无法将锁升级为独占以保存更改,因为它们被其他事务中的共享锁阻塞。我认为对于RepeatableRead隔离级别也会发生同样的情况。

如果您将隔离级别更改为ReadCommitted(在使用MS SQL Server时,SaveChanges没有TransactionScope的默认值),答案仍然是肯定的,但这次没有死锁,因为EF使用普通选择而没有任何表提示 - 这意味着选择完成时不会保留记录上的锁。只有在提交或回滚事务之前,才会锁定记录以进行保存更改(修改)。

在使用ReadCommitted事务并进行选择时,要锁定记录,您必须使用本机SQL查询和UPDLOCK(在选择期间锁定记录以进行更新,并保持该状态直到事务结束)表提示。EF查询不支持表提示。 编辑:我写了一篇关于悲观并发的长文章,其中描述了为什么您的解决方案无法正常工作以及必须更改哪些内容才能使其正常工作。

我有一个想法来准备一些测试:n个线程,每个线程进入作用域,睡眠(以便将它们全部放在作用域内),每个线程从数据库中获取一些模型,检查字段是否为null,如果是,则再次睡眠并尝试保存更改。我得到的结果是需要一些时间(对于10秒,需要约15秒),而且只有一个线程能够通过控制。我认为EF正在尝试解决死锁问题。你知道这方面的任何信息吗? - Ari
1
不,很可能是因为您从未在关键部分内获得并发执行。我有一个示例来证明它,但我决定写一篇文章来代替在我的答案中发布另一个长更新。我将在一两天内完成这篇文章并告诉您。 - Ladislav Mrnka
我非常确定那里有并发执行。所有线程几乎同时执行所有操作,然后休眠,因此它们始终在同一代码段中。但是我将等待您的文章,非常感谢。 - Ari
我添加了承诺的文章链接。 - Ladislav Mrnka

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接