【问题标题】:NHibernate timeout with SQL ServerSQL Server 的 NHibernate 超时
【发布时间】:2018-09-26 18:51:13
【问题描述】:

我有一个带有 C# 后端的 ASP.Net 应用程序,它使用 NHibernate 和 SQL Server。

最近我注意到,在网页的某个页面上长时间执行某些任务时,它会冻结并出现超时(NHibernate 异常)。

在出现超时问题后,我转到 SQL Server Management Studio,我可以在监视器中看到有几十个进程没有任何状态并且都在同一个数据库中:

我到处寻找解决方案,但我不知道我是否可能错误地处理了会话。以下是我处理会话的方式:

public static void DisposeSession()
{
    FlushSession(true); // Method that does a commit if there is a transaction
    ISession Session = CurrentSessionContext.Unbind(sessionFactory);

    if (Session != null)
    {
        Session.Close();
        Session.Dispose();
    }
}

编辑 1:

我目前正在使用 WebForms,请求之间没有共享会话,会话正在正确处理。

问题是,当我开始浏览 Web 的不同部分(因此执行新请求)时,进程开始像图像中一样增长......并且在某个时刻,网页响应运行时错误或超时。

如何控制这种行为? 每个请求只能有一个进程,并且在处理时关闭进程?

编辑 2:

我错了,会话管理是用第一个答案中提供的方法正确完成的。有进程,但它们由 NHibernate 正确管理。

【问题讨论】:

  • 无论代码的其余部分做什么,你有一个 static DisposeSession 的事实就是 gets 一个会话只是为了 不安全地处置这意味着您的数据访问代码存在严重问题。 session 与连接相同 - 您在 using() 块中尽可能短地使用它,因此无论如何它都会被释放和关闭。你分享它。如果发生异常,会话无论如何都是无用的,必须丢弃。你分享的是工厂
  • 换句话说,无法提供这些信息。您的代码正在泄漏连接,保证在出现异常时会泄漏会话,如果多个请求尝试使用同一个会话,则保证会阻止。问题不在于DisposeSession。你甚至不应该有这样的方法
  • @PanagiotisKanavos,你不知道CurrentSessionContext 是做什么的。对我来说,它看起来很可能确实从HttpContext 之类的东西中检索会话,这是 Web 应用程序中的一种常见做法,并允许在短时间内处理它们(http 请求应该很短),而无需在请求之间共享,并且如果正确处理请求结束,则不会泄漏。
  • 这一点都不难。不比通过控制器或网络表单正确处理会话保持更难。对于 MVC,我个人更喜欢使用 ActionFilter 来处理这个问题,这比在控制器中全局处理它提供更多的控制权。 http 请求通常很适合工作单元,因此将会话绑定到它非常有意义。如果http请求太长导致死锁问题,那么他们所做的业务可能有问题。但是许多其他模式也是有效的,只要它们使会话保持短暂且不共享。
  • 关于提前打开连接:是的,但问题不大。在查询发生之前不会发出锁,并且不会在读取时发出锁,除非使用诸如发出共享锁的 SQL Server 旧式读取提交模式之类的东西。 (幸运的是,SQL Server 现在支持其“已提交读快照”模式,它的工作方式有点像 Oracle 多年以来的已提交读操作,没有锁。)对于具有更强隔离模式的事务,当然它们不应该跨越 ORM UoW 之类的东西,这通常此类交易涉及到很多对象。

标签: c# asp.net sql-server nhibernate


【解决方案1】:

首先,确保您的 CurrentSessionContext 不会在请求之间共享会话。

通常这种上下文将会话存储在HttpContext.Current.Items 字典中。这是一个安全的地方,可以确保它不会最终与其他 http 请求共享。
相反,如果它在请求之间共享会话,那么一旦负载过重,您的应用程序对于大多数用户来说都会失败。
如果它使用ThreadContextCallContext,则由于“ASP.Net 线程敏捷性”导致某些http 请求切换线程并丢失其先前的ThreadContextCallContext . HttpContext.Current.Items 保证在 http 请求切换线程时保留,而不是其他线程。

如果这个CurrentSessionContext 看起来正确,那么修复你的DisposeSession。它不能确保您的会话在刷新失败的情况下关闭。

应该更像:

public static void DisposeSession()
{
    try    
    {    
        FlushSession(true); // Method that does a commit if there is a transaction
    }
    finally
    {
        ISession Session = CurrentSessionContext.Unbind(sessionFactory);

        if (Session != null)
        {
            // Dispose closes the session too. And Close dispose the transaction
            // if there was one. And transaction Dispose rollbacks if it was pending.
            Session.Dispose();
        }
    }
}

然后,检查此DisposeSession 始终被调用,无论您的请求以何种方式结束。特别是检查触发异常的请求会发生什么。这包括一些使用ThreadAbortException 的重定向案例,例如Response.Redirect("...")

会话管理:

关于我通常使用的会话管理模式,我将其绑定到依赖注入,具有每个请求的生命周期和一个 http 模块,确保无论请求的结果如何都能处理它,事务由操作过滤器 (MVC) 处理.

你的模式远非如此。如果您想更改它,您可以更轻松地关注此 blog post series 来自 NHibernate 的老贡献者和 NHibernate 分析器的作者。

【讨论】:

  • 您编写的内容适用于实体框架,但不是 NHibernate。与 EF 上下文不同,会话是一个烫手山芋,必须小心处理。异常和线程安全是主要问题 - 单个数据库错误,您必须创建一个 new 会话。旧的没用。这意味着你不能注入会话,你必须注入工厂。
  • 无论我处理 EF 还是 NH,在会话或上下文失败时,我总是将其丢弃(处置)。并且让它保持在那个状态,因为我让异常流动并且我的错误页面不需要会话。当我在失败后和同一请求内需要一些数据库交互的特定情况下,具有该需求的类依赖于会话工厂并获得仅用于该特定需求并在之后立即处理的新会话。 (顺便说一句,我很可能也永远不会相信失败的 EF 上下文。)
  • EF 上下文不会失败。它们异常安全的。如果你想从死锁中恢复,你可以。但是,对于 NH,如果您无权访问 factory,则必须重新启动请求。 factory 相当于 DbContext,而不是 session
  • 无论如何,我怀疑 OP 看到了由于会话处理延迟而创建的池连接,并且 连接 没有引起实际问题。甚至没有异常堆栈跟踪,我猜还有其他事情发生。也许是全球交易?
  • 也许吧。无论如何,摆脱陈旧的连接将有助于排除这种情况并找到真正的原因。或者是时候请 DBA 进行调查了。
猜你喜欢
  • 2011-12-22
  • 2012-02-29
  • 2015-10-12
  • 1970-01-01
  • 1970-01-01
  • 2010-11-24
  • 2011-02-23
  • 2012-07-04
  • 2012-07-06
相关资源
最近更新 更多