交易死锁,如何正确设计?

时间:2012-08-28 19:49:46

标签: c# .net multithreading entity-framework-4.1 deadlock

所以我正在研究这个实体框架项目,它将被用作DAL的类型,并且在运行压力测试时(通过Thread()开始对实体进行一些更新)并且我得到了这些:

  

_innerException = {“事务(进程ID 94)在锁资源上与另一个进程发生死锁,并被选为死锁牺牲品。重新运行该事务。”}

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

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新手。但我认为我的问题是上下文的设计问题。

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

这对我来说似乎不是“共享”,因为我在每个方法中使用新的BackEndEntities() ,所以我需要更改哪些内容才能使其更加健壮?

此DAL将用于互联网上公开的Web服务(在对coure进行代码审查之后),因此我无法控制它将被强调多少以及许多不同的实例可能想要更新同一实体。 / p>

谢谢!

3 个答案:

答案 0 :(得分:8)

thouse死锁的原因不是您的代码,而是由于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

答案 1 :(得分:6)

在大系统中,死锁自由是一个非常难的问题。它本身与EF无关。

缩短事务的生命周期可以减少死锁,但会导致数据不一致。在那些你以前陷入僵局的地方,你现在正在销毁数据(没有任何通知)。

因此,请根据逻辑事务选择上下文生命周期和事务生存期,而不是根据物理因素。

启用快照隔离功能。这使得阅读交易完全脱离了等式。

要编写事务,您需要找到锁定顺序。通常情况下,它是悲观锁定和更高级别的最简单方法。示例:您是否始终在客户的上下文中修改数据?将该客户的更新锁定作为您的交易的第一个声明。这通过序列化对该客户的访问来提供完全的死锁自由。

答案 2 :(得分:1)

上下文是赋予实体与数据库通信的能力,没有上下文,没有什么概念在哪里。因此,启动上下文是一件大事,它占用了大量资源,包括数据库等外部资源。我相信你的问题是'新'命令,因为你会有多个线程试图启动并获取相同的数据库资源,这肯定会死锁。

您发布的代码似乎是一种反模式。它的外观,你的实体上下文相对较快地启动并超出范围,而你的存储库CRUD对象似乎持续了很长时间。

我实现Entity for的公司传统上完全采用相反的方式完成 - 只要程序集需要数据库就创建并保存Context,并且创建存储库CRUD对象并在几微秒内完成

我不能说你在哪里断言上下文没有被共享,所以我不知道下面说的是什么情况,但你绝不应该在程序集之间共享上下文在同一个程序集中,我看不出有什么理由可以解释启动上下文需要多少资源,以及执行此操作需要多长时间。实体上下文非常繁重,如果你通过单线程使你当前的代码工作,我怀疑你会看到一些绝对残酷的表现。

所以我建议改为重构这个你有Create(BackEndEntites context)Update(BackEndEntities context),然后让你的主线程(制作所有这些子线程的线程)创建并维护一个BackEndEntities上下文来传递和它的孩子们一样。另外,请确保在完成这些操作后立即删除AgentControllerOrderController,并且永远不要在方法之外重复使用它们。实现像Ninject或StructureMap这样的控制框架的良好反转可以使这更容易。