如何在nhibernate中复制和重试死锁

时间:2011-03-18 14:04:33

标签: nhibernate deadlock

查看我的日志,我可以看到我的应用程序容易出现死锁。它们出现在我的应用程序的许多部分中。

1)有没有办法复制这个问题。即:我只在日志中看到过这个。

2)如果交易被锁定,重试的最佳/最简单方法是什么

3)如果我用try / catch包裹了这个电话。异常类型是什么。

有很多关于这个问题的文章。我总结说,最好的选择是尽可能地尝试缩短交易。我应该改变隔离级别吗?

2 个答案:

答案 0 :(得分:14)

发现死锁

很难找到死锁。如果您知道它们发生的原因,您可以在集成测试中重现它。在实际环境中,您可以使用Profiler来观察死锁。它显示了一个显示死锁形成方式的图表。

重试

你应该扔掉交易并重新开始。任何数据库异常后,NHibernate会话都不同步。

我们在重新启动之前有一段延迟,以避免对数据库造成更多压力。它等待一段包含随机数的时间,以避免并行事务再次同步。

避免死锁

缩短锁定时间

如果您使用的是Sql Server,由于其悲观的锁定机制(与Oracle数据库相比),它很容易受到死锁的影响。较新的Snapshot隔离级别与Oracle正在进行的操作类似,可能会在一定程度上解决问题,但直到现在我才开始使用,所以我不能说太多。

NHibernate通过缓存对持久数据的更改并在事务结束时存储它来尽可能地解决问题。但是有一些限制和一些方法可以打破它。

使用标识(“自动编号”)作为主键可能是most famous mistake。它强制NH在将实体放入会话时插入实体,从而产生整个表的锁(在SQL Server中)。

更复杂的问题是冲洗问题。 NH需要在执行查询之前刷新更改,以确保一致性。您可以通过将FlushMode设置为Never来解决此问题,这可能会导致一致性问题,因此只有在您确切知道自己的操作时才会这样做。最佳解决方案是仅使用GetLoad或导航到根实体的属性,而不是在事务中间执行查询。

通过这一切,NH能够等待数据库的任何插入,更新和删除命令,直到事务结束。这大大减少了锁定时间,因此也降低了死锁的风险。

避免死锁的一般规则

使用NHibernate时,避免死锁的一般规则也适用。最重要的是:以特定顺序锁定资源,锁定资源不是由一个锁定,而是在开始时锁定所有资源。后者与我上面所说的减少锁定时间相矛盾。这意味着您在事务开始时锁定资源以使其他事务等待直到完成。这可以减少死锁,但也可以减少并行执行。

答案 1 :(得分:1)

这是我们选择在遗留系统中使用的解决方案,我们无法解决这些死锁的根本原因,因为这意味着重写了许多现有的和记录不完整的代码。系统使用的是DataSets和ADO.NET类,所以如果你打算使用NHibernate,我担心你必须研究它的内部和/或开发你自己的扩展,或者如果没有现有的功能。

1)如果代码容易出现死锁,则应该开始出现足够的数据库负载。使用有问题的过程需要许多同时使用相同表的连接。

很难在您想要的确切位置重现死锁,但如果您想要重试过程测试的一般死锁,请同时从10个具有不同访问顺序的线程中读取/插入到相同的表中(例如,表A然后是B in其中一些,表B,然后是其他A),延迟时间很短,你很快得到一个。

2)您需要重试与事务一起使用的整个代码片段,并重试数据初始化。这意味着,如果要在事务中填充数据集,则必须在可重试代码块的开头清除它们。

3)SqlException的.Numer = 1205。通常,您还可以重试超时和网络错误:

        switch (sqlEx.Number)
        {
            case 1205:
            {
                DebugLog("DEADLOCK!");
                canRetry = true;
                break;
            }
            case -2:
            case -2147217871:
            {
                DebugLog("TIMEOUT!");
                canRetry = true;
                break;
            }
            case 11:
            {
                DebugLog("NETWORK ERROR!");
                canRetry = true;
                break;               
            }
            default:
            {
                DebugLog(string.Format("SQL ERROR: {0}", sqlEx.Number));
                break;
            }
        }

根据我在重试死锁时的经验,最好使用SqlConnection.ClearPool(连接)丢弃池中的连接,因为下次可能无法正确重置。