【问题标题】:isn't SaveChanges() managed by transactions?SaveChanges() 不是由事务管理吗?
【发布时间】:2021-11-16 17:43:56
【问题描述】:

我有几个“线程”(即 Hangfire 任务)将并行运行这种函数(每个调用传递不同的 systemId):

private void UpdatePartners(int systemId)
{
    try
    {
        using (var ctx = new MyEntities())
        {
            var partners = ctx.PartnersUpdate.Select(s => s)
              .Where(w => w.SystemId.Equals(systemId) && statusToEvaluate.Contains(w.Status)).OrderByDescending(p => p.UpdateAt).Take(500);

            var partnersId = partners.Select(p => p.PartnerId).ToList();

            var partnersToUpdate = ctx.vPartnersSystems.AsNoTracking().Select(s => s)
                    .Where(w => w.SystemId.Equals(systemId) && partnersId.Contains(w.Id) && w.Validity.Equals(true))
                    .ToList();

            foreach (var partner in partners)
            {
                // some operations on "partner", using data from "partnersToUpdate"
                
                ctx.PartnersUpdate.AddOrUpdate(partner);
            }

            ctx.SaveChanges();
        }
    }
    catch (Exception ex)
    {
        Logger(ex, "Error on UpdatePartners");
    }
}

很遗憾,有时我会收到此错误:

UpdatePartners(Int32 systemId) in C:\myProjects\XYZ\Controllers\TestController.cs:line 16 Void HandleReaderException(System.Exception)
System.Data.SqlClient.SqlException (0x80131904): Transaction (Process ID 130) was deadlocked on lock | communication buffer resources with another process and has been chosen as the deadlock victim. Rerun the transaction.

似乎在迭代合作伙伴时(但我可能是错的):

foreach (var partner in partners)

为什么会这样?如果UpdatePartners() 与不同的systemId 并行调用(这意味着查询每次都会捕获不同的记录),为什么它会处理“死锁”?

读取/更新的“相同行”不是死锁吗?

【问题讨论】:

  • 首先要尝试的显而易见的事情是将ToList(Async) 添加到产生partners 的查询中。这可以防止在执行 foreach 循环时保持打开状态。作为奖励,它还可以防止您执行两次。

标签: .net entity-framework-6 deadlock .net-4.6.1


【解决方案1】:

一个 DbContext 实例使用事务进行操作,但是如果您有多个任务运行每个访问 DbContext 实例,这些任务都针对同一个表进行操作,您可能会遇到死锁。

我看到的一个问题是,如果您根据替代来源检查合作伙伴并进行更改,则不应调用 AddOrUpdate。合作伙伴已经包含跟踪的实例,因此只需调用SaveChanges()。 AddOrUpdate 仅用于为数据库播种。

您需要非常小心地运行类似这样的批量操作与其他数据操作并行运行。您一次加载的要处理的实体越多,EF 执行读取和保存所需的时间就越长,死锁的窗口就越大。一次性处理 500 条记录是相当少的。如果此任务不断检查和更新行,您可能希望减少它,并且如果没有要处理的行,也可以让它在没有进一步查询的情况下退出。

如果这是 SQL Server,您可能还需要考虑检查数据库是否设置为读取已提交快照。这可以通过利用行版本控制处理并发来帮助防止死锁情况。

【讨论】:

    【解决方案2】:

    正如您提到的,您有时会使用partnersToUpdate,您应该删除AsNoTracking() 选项,因为实体框架丢失了实体的跟踪,并且无法更新它们

    【讨论】:

    • 不,没关系。我只从中获取 id (它不会改变,作为 Key)。无论如何,这个答案对我的问题没有任何帮助
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-11-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多