【问题标题】:Can NHibernate be configured not to open a new MySQL connection for each query?可以将 NHibernate 配置为不为每个查询打开一个新的 MySQL 连接吗?
【发布时间】:2017-05-10 19:01:14
【问题描述】:

我们有一个使用 Asp.net Web API 构建的 Web 服务。我们使用 NHibernate 作为连接 MySQL 数据库的 ORM。

我们有几个控制器方法可以执行大量 (1,000-3,000) 相对便宜的查询。

我们正在考虑提高这些控制器方法的性能,并且几乎所有时间都花在了 NHibernate 查询上,因此这是我们关注的重点。

从中期来看,解决方案是减少查询数量(可能通过减少更大的查询)和/或并行化查询(这需要一些工作,因为 NHibernate 没有异步 api 并且会话是单一的线程)和类似的东西。

在短期内,我们希望在不承担任何一个大型项目的情况下提高性能。

我们进行了一些性能分析,并惊讶地发现每个查询中的很多时间(超过一半)都花在了打开与 MySQL 的连接上。

似乎 NHibernate 正在为每个查询打开一个到 MySQL 的新连接,并且 MySqlConnection.Open() 每次打开连接时都会对数据库进行两次往返(即使连接来自池)。

这是我们的一个性能配置文件的屏幕截图,您可以在其中看到这两件事:

我们想知道这是否是预期的,或者我们是否遗漏了诸如 NHibernate 的错误配置/误用之类的东西,或者是在 MySqlConnection.Open() 中消除到数据库的两次往返的方法。

我做了一些额外的挖掘,发现了一些有趣的东西:

如果我们将.SetProperty(Environment.ReleaseConnections, "on_close") 添加到 NHibernate 配置中,则不再调用 Open(),并且执行查询所需的时间减少了 40% 以上。

这似乎不是推荐的设置:http://nhibernate.info/doc/nhibernate-reference/transactions.html#transactions-connection-release

根据文档,如果我将读取包装在单个 NHibernate 事务中,我希望得到相同的行为(无需额外调用 Open()),但我无法让它工作。这是我在控制器方法中所做的:

using (var session = _sessionFactory.OpenSession()) {
    using (var transaction = session.BeginTransaction()) {

        // controller code

        transaction.Commit();
    }
}

关于如何使用 NHibernate 的推荐配置获得相同行为的任何想法?

在深入研究之后发现我的测试实现中有一个错误,在使用事务修复它之后,按预期消除了对 Open() 的额外调用。

【问题讨论】:

    标签: mysql asp.net-web-api nhibernate


    【解决方案1】:

    不使用事务被认为是一种不好的做法,所以无论如何都欢迎开始添加它们。

    此外,正如您自己发现的那样,默认的连接释放模式auto 目前总是转换为AfterTransaction,它与 NHibernate(至少 v2 到 v4)在没有事务时在每个语句之后释放连接会议正在进行中。

    来自Connection Release Modes

    请注意,对于ConnectionReleaseMode.AfterTransaction,如果会话被认为处于自动提交模式(即未启动事务),则每次操作后都会释放连接。

    因此,只需处理您的会话使用情况即可。由于您的应用程序不是这种情况,我怀疑还有其他问题。

    您的控制器代码是否使用其他会话? NHibernate 显式事务仅适用于启动它们的会话(或从具有ISession.GetSession(EntityMode.Poco) 的会话打开的会话)。
    所以你需要为每个打开的会话处理一个事务。

    您可以使用TransactionScope 代替将多个会话包装在一个事务中。但是每个会话仍然会打开一个专用连接。在大多数情况下,这会将事务提升为分布式事务,这会降低性能并且如果您的服务器未配置为启用它可能会失败。

    您可以配置和使用contextual session,而不是将每个控制器操作的多个会话仅替换为一个。当然你也可以使用依赖注入来实现这一点。

    注意事项:

    关于减少应用程序发出的查询数量,NHibernate 中有一些易于利用的功能:

    在我看来,并行开发 Web API 是一条注定失败的道路。线程是 Web 应用程序的宝贵资源。请求使用的线程越多,Web 应用程序能够并行处理的请求就越少。因此,采用这种方式很可能会严重影响您的应用程序可扩展性。

    不建议使用OnClose 模式,因为它会延迟连接释放到会话关闭,这可能会在最后一个事务之后很晚发生,尤其是在使用上下文会话时。由于看起来您的会话使用非常本地化,可能在非常接近最后一个查询时关闭,所以这对您的应用程序来说应该不是问题。

    【讨论】:

    • 感谢您的详细解释。我们做了更多的挖掘,结果发现我们的问题是我的问题中的事务测试不起作用的原因是测试实现中的一个简单错误。一旦我找到并修复它,它就会按预期工作。我们肯定会转向使用显式事务进行读取(我们已经将它们用于写入),并探索进一步提高性能的选项。
    【解决方案2】:

    并行化查询(从 NHibernate 开始这需要一些工作 没有异步 api,会话是单线程的)和 诸如此类。

    您可以使用 NHibernate Futures 推迟查询的执行,

    以下代码(摘自 reference 文章)将执行单个查询,尽管检索到 2 个值,

    using (var s = sf.OpenSession())
    using (var tx = s.BeginTransaction())
    {
        var blogs = s.CreateCriteria<Blog>()
            .SetMaxResults(30)
            .Future<Blog>();
        var countOfBlogs = s.CreateCriteria<Blog>()
            .SetProjection(Projections.Count(Projections.Id()))
            .FutureValue<int>();
    
        Console.WriteLine("Number of blogs: {0}", countOfBlogs.Value);
        foreach (var blog in blogs)
        {
            Console.WriteLine(blog.Title);
        }
    
        tx.Commit();
    }
    

    也可以使用NHibernate Batching来减少查询次数

    【讨论】:

    • “NHibernate Batching”链接的博客已过时,配置参数当前命名为adonet.batch_size。也许这个功能对于 MySql,OP DB 仍然不可用。
    猜你喜欢
    • 1970-01-01
    • 2017-09-27
    • 2011-05-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多