【问题标题】:NHibernate and ADO.NET Connection PoolingNHibernate 和 ADO.NET 连接池
【发布时间】:2011-09-27 09:52:48
【问题描述】:

NHibernate 似乎没有对 ADO.NET 数据库连接进行池化。只有在事务提交或回滚时才会关闭连接。对源代码的审查表明,无法配置 NHibernate 以便在释放 ISession 时关闭连接。

这种行为的意图是什么? ADO.NET 本身具有连接池。没有必要在事务中一直保持它们打开。使用这种行为也会创建不必要的分布式事务。因此,http://davybrion.com/blog/2010/05/avoiding-leaking-connections-with-nhibernate-and-transactionscope/ 中描述的一种可能的解决方法不起作用(至少不适用于 NHibernate 3.1.0)。我正在使用 Informix。似乎每个其他数据库都存在同样的问题 (NHibernate Connection Pooling)。

是否有任何其他解决方法或建议可以避免此问题?

这是重现问题的单元测试:

  [Test]
  public void DoesNotCloseConnection()
  {
     using (SessionFactoryCache sessionFactoryCache = new SessionFactoryCache())
     {
        using (TransactionScope scope = new TransactionScope(TransactionScopeOption.Required, new TransactionOptions() { IsolationLevel = IsolationLevel.ReadCommitted, Timeout = TimeSpan.FromMinutes(10) }))
        {
           fixture.Setup(); // Creates test data

           System.Data.IDbConnection connectionOne;
           System.Data.IDbConnection connectionTwo;

           using (ISessionFactory sessionFactory = sessionFactoryCache.CreateFactory(GetType(), new TestNHibernateConfigurator()))
           {
              using (ISession session = sessionFactory.OpenSession())
              {
                 var result = session.QueryOver<Library>().List<Library>();
                 connectionOne = session.Connection;
              }
           }

           // At this point the first IDbConnection used internally by NHibernate should be closed

           using (ISessionFactory sessionFactory = sessionFactoryCache.CreateFactory(GetType(), new TestNHibernateConfigurator()))
           {
              using (ISession session = sessionFactory.OpenSession())
              {
                 var result = session.QueryOver<Library>().List<Library>();
                 connectionTwo = session.Connection;
              }
           }

           // At this point the second IDbConnection used internally by NHibernate should be closed

           // Now two connections are open because the transaction is still running
           Assert.That(connectionOne.State, Is.EqualTo(System.Data.ConnectionState.Closed)); // Fails because State is still 'Open'
           Assert.That(connectionTwo.State, Is.EqualTo(System.Data.ConnectionState.Closed)); // Fails because State is still 'Open'
        }
     }
  }

处理 NHibernate-Session 什么都不做,因为我们仍在事务中

SessionImpl.cs:

public void Dispose()
    {
        using (new SessionIdLoggingContext(SessionId))
        {
            log.Debug(string.Format("[session-id={0}] running ISession.Dispose()", SessionId));
            if (TransactionContext!=null)
            {
                TransactionContext.ShouldCloseSessionOnDistributedTransactionCompleted = true;
                return;
            }
            Dispose(true);
        }
    }

注入自定义 ConnectionProvider 也将不起作用,因为调用 ConnectionProvider 的 ConnectionManager 有几个前提条件,检查不允许在事务中关闭连接。

ConnectionManager.cs:

public IDbConnection Disconnect() {
        if (IsInActiveTransaction)
            throw  new InvalidOperationException("Disconnect cannot be called while a transaction is in progress.");

        try
        {
            if (!ownConnection)
            {
                return DisconnectSuppliedConnection();
            }
            else
            {
                DisconnectOwnConnection();
                ownConnection = false;
                return null;
            }
        }
        finally
        {
            // Ensure that AfterTransactionCompletion gets called since
            // it takes care of the locks and cache.
            if (!IsInActiveTransaction)
            {
                // We don't know the state of the transaction
                session.AfterTransactionCompletion(false, null);
            }
        }
    }

【问题讨论】:

  • 据我所知,数据库需要相同的连接才能使用事务。因此,只要事务正在运行,它就保持连接处于活动状态,我并不觉得奇怪?如果连接返回到池中,则无法确保您第二次从池中收到相同的连接。
  • 但是,在您的具体测试中,您正在检查底层 IdbConnection,我认为它是 ADO.NET 的一部分,而在这种情况下您正在测试的不是 ADO.NET 连接池?你应该做的是创建两个不同的会话(同样来自同一个工厂,确保是这种情况)并确保你收到相同的连接。
  • 在每个事务开始时,Driver 类(在我的例子中是 OdbcDriver)创建一个新的 DbConnection (OdbcConnection)。此连接保持打开整个事务,这是不必要的。我编写的测试实际上使用了来自一个 SessionFactory 的两个不同的会话。
  • 我仍然不确定检查 .State 是否是公平的测试。查看msdn.microsoft.com/en-us/library/… 它说:“在打开的连接上调用 State 属性会增加应用程序开销,因为每个这样的调用都会导致对底层 ODBC 驱动程序的 SQL_ATTR_CONNECTION_DEAD 调用以确定连接是否仍然有效。”这表明“已关闭”连接可能是死连接,因此不可重用于池。
  • 进一步:msdn.microsoft.com/en-us/library/… 表示以下内容:“当对连接执行最近的网络操作时,指示 SqlConnection 的状态。”这使我认为,在这种情况下,状态实际上并不代表连接的当前状态,并且不能用于确保连接对池可用。池也可以是一个驱动特性,我知道 Npgsql 已经内置了连接池,并且不依赖 ADO.NET。

标签: .net nhibernate ado.net connection-pooling


【解决方案1】:

NHibernate 有两种“模式”。

  • 要么在应用程序中打开连接,然后由应用程序来管理它。在将连接传递给sessionfactory.OpenSession(connection) 时使用此“模式”。
  • 或者连接是由 NH 创建的。然后在会话关闭时关闭。此“模式”在未将连接传递到sessionfactory.OpenSession() 时使用

TransactionScope 有一些支持。它最有可能使用第一个“模式”。可能连接不是由 NH 持有,而是由事务范围持有。具体我不知道,我不使用环境事务。

NH顺便使用了ADO.NET连接池。

您还可以使用ISession.Disconnect() 断开会话并使用ISession.Reconnect() 重新连接。

documentation 中你会发现:

方法 ISession.Disconnect() 将断开会话与 ADO.NET 连接并将连接返回到池中(除非您 提供了连接)。

【讨论】:

  • 感谢您的回复,但我认为您对“第二”模式有误。当会话被释放时,连接保持打开状态。正如您在我在上面发布的代码(ConnectionManager.cs)中看到的那样,不可能在事务范围内断开/重新连接。在您提供的链接中,还说您必须先提交/中止事务,然后才能再次断开/重新连接。连接 (IDbConnection) 由 ConnectionManager 类作为私有成员控制,而不是由事务控制。
【解决方案2】:

您可以通过将以下设置添加到连接字符串来完成此操作。

Pooling=true; 
Min Pool Size=3;
Max Pool Size=25;
Connection Lifetime=7200;
Connection Timeout=15;
Incr Pool Size=3;
Decr Pool Size=5;

池:为您的应用启用池

Min Pool:即使所有会话都关闭,也要保持打开的最小连接数。

最大池:应用程序将打开到数据库的最大连接数。当达到最大值时,它将等待连接超时指定的秒数,然后抛出异常。

连接超时:等待池中空闲连接的最长时间(以秒为单位)

Connection Lifetime:当一个连接返回到池中时,它的创建时间与当前时间进行比较,如果这个时间跨度(以秒为单位)超过了 Connection Lifetime 指定的值,则该连接被销毁。值为零 (0) 会导致池连接具有最大连接超时。

Incr Pool Size:控制使用所有连接时建立的连接数。

Decr Pool Size:控制在过多的已建立连接未使用时关闭的连接数。

http://www.codeproject.com/Articles/17768/ADO-NET-Connection-Pooling-at-a-Glance

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-10-04
    • 1970-01-01
    • 1970-01-01
    • 2010-09-14
    • 2017-06-14
    • 2015-02-26
    • 1970-01-01
    相关资源
    最近更新 更多