事务死锁,如何正确设计?

13

我正在开发一个使用 Entity Framework 作为数据访问层的项目,当运行压力测试(通过 Thread() 开始一些实体更新操作)时会出现以下错误:

 

_innerException = {"Transaction (Process ID 94) was deadlocked on lock resources with another process and has been chosen as the deadlock victim. Rerun the transaction."}

以下是我实现类方法的一些示例:

public class OrderController
{

    public Order Select(long orderID)
    {
        using (var ctx = new BackEndEntities())
        {

            try
            {
                var res = from n in ctx.Orders
                                       .Include("OrderedServices.Professional")
                                       .Include("Agency")
                                       .Include("Agent")
                          where n.OrderID == orderID
                          select n;
                return res.FirstOrDefault();
            }
            catch (Exception ex)
            {
                throw ex;
            }
         }
    }

    public bool Update(Order order)
    {
        using (var ctx = new BackEndEntities())
        {
            try
            {
                order.ModificationDate = DateTime.Now;
                ctx.Orders.Attach(order);
                ctx.SaveChanges();
                return true;
            }
            catch (Exception ex)
            {
                throw ex;
            }
        }
    }
}

并且:

public class AgentController
{

    public Agent Select(long agentID)
    {
        using (var ctx = new BackEndEntities())
        {
            try
            {
                var res = from n in ctx.Agents.Include("Orders")
                          where n.AgentID == agentID
                          select n;
                return res.FirstOrDefault();
            }
            catch (Exception ex)
            {
                throw ex;
            }
        }

    }

    public bool Update(Agent agent)
    {
        using (var ctx = new BackEndEntities())
        {
            try
            {
                agent.ModificationDate = DateTime.Now;
                ctx.Agents.Attach(agent);
                ctx.ObjectStateManager.ChangeObjectState(agent, System.Data.EntityState.Modified);
                ctx.SaveChanges();
                return true;
            }
            catch (Exception ex)
            {
                throw ex;
            }
        }
    }
}

显然,这里的代码可能可以更好,但我是 EF 新手。但我认为我的问题更多是与上下文设计相关。

我记得这里有人提到过,如果我的上下文不是共享的,我就不会遇到这些死锁问题。

对我来说,这似乎不是“共享”的,因为我在每个方法中都使用了 using new BackEndEntities(),那么我需要改变什么才能使它更加健壮呢?

这个数据访问层将被用于一个暴露在互联网上的 Web 服务(经过代码审查),所以我无法控制它将受到多大的压力,并且许多不同的实例可能想要更新相同的实体。

谢谢!


1
出于各种原因,每个HTTP请求应该共享一个上下文。不要太长也不要太短。 - usr
3个回答

9
那些死锁并不是你的代码问题,而是由于EF使用SERIALIZABLE作为默认TransactionScope隔离级别导致的。
SERIALIZABLE是最严格的锁定方式,这意味着你默认选择了最严格的隔离级别,所以你可能会遇到很多锁定情况!
解决方案是根据你要执行的操作指定另一个TransactionScope。你可以像这样包装你的EF操作:
using (var scope = new TransactionScope(TransactionScopeOption.Required, new 
        TransactionOptions { IsolationLevel= IsolationLevel.Snapshot }))
{
    // do something with EF here
    scope.Complete();
}

阅读更多有关此问题的信息:

http://blogs.msdn.com/b/diego/archive/2012/04/01/tips-to-avoid-deadlocks-in-entity-framework-applications.aspx

http://blogs.u2u.be/diederik/post/2010/06/29/Transactions-and-Connections-in-Entity-Framework-40.aspx

http://blog.aggregatedintelligence.com/2012/04/sql-server-transaction-isolation-and.html

https://serverfault.com/questions/319373/sql-deadlocking-and-timing-out-almost-constantly

(注:以上内容为相关IT技术方面的文章链接,旨在提供相关信息,无需解释。)

6
死锁问题在大型系统中是一个非常困难的问题,这与 EF 本身无关。
缩短事务的生命周期可以减少死锁,但会导致数据不一致。在以前发生死锁的地方,现在你正在销毁数据(没有任何通知)。
因此,根据逻辑事务而不是物理考虑选择上下文生命周期和事务生命周期。
打开快照隔离。这将完全排除读取事务。
对于写入事务,您需要找到一种锁定顺序。通常最简单的方式是悲观地锁定并在更高层次上进行锁定。例如:您总是在客户环境中修改数据吗? 将一个更新锁定置于该客户的第一条语句中,可以提供完全的死锁自由,使访问该客户的串行化。

快照隔离解决了所有问题。我可以使用这个数据访问层运行200个线程的.NET测试应用程序,更新相同的实体,而没有一个崩溃。大多数死锁是否都发生在读取事务上?这是否意味着SQL Server处理大量更新比大量选择更好? - Francis Ducharme
1
不,任何涉及写操作的工作负载都可能发生死锁。在你的情况下,死锁消失是巧合。可能是因为你有许多小型只读事务被完全排除在外了。 - usr

2
上下文是赋予实体与数据库交互能力的基础,没有上下文就没有概念来区分位置。因此,启动上下文是一件大事,它占用了许多资源,包括数据库这样的外部资源。我认为你的问题在于'new'命令,因为你可能有多个线程试图启动并获取相同的数据库资源,这肯定会死锁。
你发布的代码看起来像是反模式。它看起来像是你的Entity Context很快就会启动并超出范围,而你的存储库CRUD对象似乎会持续更长时间。
我所实现的公司传统上完全相反-Context被创建并保留了作为程序集需要数据库的时间,而存储库CRUD对象则会在微秒级别创建并消失。
我不知道你从哪里得到上下文不能共享的主张,所以我不知道那是在什么情况下说的,但绝对正确的是你不应该跨程序集共享上下文。对于同一个程序集,考虑到启动上下文所需的资源和时间,我看不出任何理由为什么你不会共享上下文。Entity Context非常重,如果你通过单线程使当前代码工作,我怀疑你会看到一些极其可怕的性能。
因此,我建议你重构代码,使你有Create(BackEndEntites context)Update(BackEndEntities context),然后让你的主线程(制作所有这些子线程的线程)创建和维护一个BackEndEntities上下文来传递给它的子线程。同时确保你在完成它们之后立即摆脱你的AgentControllerOrderController,并且永远不要在方法之外重复使用它们。实现一个好的控制反转框架,如Ninject或StructureMap,可以使这个过程变得更容易。

谢谢,非常有用。我会按照您的建议进行重构。 :) - Francis Ducharme

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