加载大型集合时出现nHibernate性能问题

时间:2011-04-14 13:36:55

标签: nhibernate

(只是为了说清楚:我的应用程序并不是关于员工和部门。我只是为了这个例子而使用这些术语)。
每个部门都有一个员工集合,这是一个懒惰的负载。每当我添加一个新员工时,我想确保它在集合中不存在,所以我将集合加载到内存并对其进行检查。
问题是在生产环境中,我有一些拥有10,000多名员工的部门 我发现获取集合然后保存新员工需要花费很多时间 我做了一些实验,在其中我将nH生成的完全相同的 select语句复制到ADO.Net SQLDataAdapter。结果如下:

***16:04:50:437*** DEBUG NHibernate.SQL - SELECT ... FROM dbo.[Employee] emp0_ left outer join dbo.[Department] department1_ on emp0_.Department_id=department1_.Id left outer join dbo.[TableC] TableC2_ on department1_.TableC_id=TableC2_.Id WHERE emp0_.SomeField_id=@p0;@p0 = 2
***16:05:00:250*** DEBUG NHibernate.SQL - SELECT ... FROM dbo.TableD codeshared0_ left outer join dbo.[Department] department1_ on codeshared0_.Department_id=department1_.Id left outer join dbo.[TableC] TableC2_ on department1_.TableC_id=TableC2_.Id WHERE codeshared0_.Employee_id in (select emp0_.Id FROM dbo.[Employee] emp0_ left outer join dbo.[Department] department1_ on emp0_.Department_id=department1_.Id left outer join dbo.[TableC] TableC2_ on department1_.TableC_id=TableC2_.Id WHERE emp0_.SomeField_id=@p0);@p0 = 2
16:05:04:984 DEBUG NHibernate.SQL - Reading high value:select next_hi from dbo._uniqueKey with (updlock, rowlock)
16:05:05:078 DEBUG NHibernate.SQL - Updating high value:update dbo._uniqueKey set next_hi = @p0 where next_hi = @p1;@p0 = 10686, @p1 = 10685
***16:05:05:328*** DEBUG MyApp.Managers - commiting
16:05:12:000 DEBUG NHibernate.SQL - INSERT INTO dbo.[Employee] (...) VALUES (@p0, @p1, @p2, @p3, @p4, @p5, @p6, @p7, @p8, @p9);@p0 = 23/04/2011 04:04:49, @p1 = 23/04/2011 03:34:49, @p2 = 23/04/2011 04:04:49, @p3 = 23/04/2011 03:34:49, @p4 = '', @p5 = False, @p6 = 433, @p7 = NULL, @p8 = 2, @p9 = 10685
16:05:12:140 DEBUG NHibernate.SQL - UPDATE dbo.[Employee] SET Department_id = @p0 WHERE Id = @p1;@p0 = 2, @p1 = 10685
16:05:12:343 DEBUG MyApp.Managers - success
16:05:12:359 DEBUG MyApp.Tests - ------------------------------------------------------------
16:05:12:359 DEBUG MyApp.Tests - Finished nHib stuff- now switching to ADO 
16:05:12:359 DEBUG MyApp.Tests - starting SQL: SELECT ... FROM dbo.[Employee] emp0_ left outer join dbo.[Department] department1_ on emp0_.Department_id=department1_.Id left outer join dbo.[TableC] TableC2_ on department1_.TableC_id=TableC2_.Id WHERE emp0_.SomeField_id=2
16:05:14:750 DEBUG MyApp.Tests - total rows received: 10036
16:05:14:750 DEBUG MyApp.Tests - SQL: SELECT ... FROM dbo.TableD codeshared0_ left outer join dbo.[Department] department1_ on codeshared0_.Department_id=department1_.Id left outer join dbo.[TableC] TableC2_ on department1_.TableC_id=TableC2_.Id WHERE codeshared0_.Employee_id in (select emp0_.Id FROM dbo.[Employee] emp0_ left outer join dbo.[Department] department1_ on emp0_.Department_id=department1_.Id left outer join dbo.[TableC] TableC2_ on department1_.TableC_id=TableC2_.Id WHERE emp0_.SomeField_id=2)
16:05:15:250 DEBUG MyApp.Tests - total rows received: 2421

你可以看到 - 使用nH需要大约15秒,而使用ADO.Net大约需要2秒。
从研究中我知道nH可能并不意味着用于在会话中存储那么多项目。您是否可以考虑此问题的任何其他可能原因,或者除了在数据库级别过滤员工之外的其他建议?

感谢

- 的修改 -
根据以下建议我尝试使用Reflection Optimizer(没有区别),并且IStatelessSession用于加载我的集合(抛出异常 - 无法通过无状态会话获取集合。)。
我认为我在Department类中的代码必须从clean清除:

if (this.Employees.Contains(emp))
{
  ...
}  

这个“更脏”的版本:

var employeesRepository = IOCContainer.Get<IEmployeesRepository>();  
if (employeesRepository.EmployeeExists(this,emp))
{
  ...
}  

任何人都有更好的建议吗?

3 个答案:

答案 0 :(得分:1)

我会使用StatelessSession和批量优化。

  

会话将跟踪所有内容   加载对象,如果我们加载很多   数据,它最终会爆发   内存不足异常。   幸运的是,NHibernate有一个现成的   解决这个问题,无国籍   会话。代码现在看起来像这样:

using (IStatelessSession s = sessionFactory.OpenStatelessSession())
{
    var books = new ActionableList<Book>(book => Console.WriteLine(book.Name));
    s.CreateQuery("from Book")
        .List(books);

}
  

无国籍会议,不像   正常的NHibernate会话,没有   跟踪加载的对象,所以   这里的代码和数据读取器代码是   基本上是一样的。

对于批量优化及更多:NHibernate performance tricks

答案 1 :(得分:1)

您没有理由将所有的empoyees加载到内存中。你应该写一个查询 使用HQL / Critiria API / Linq到NHibernate来检查员工是否已经存在于DB中。 例如:

var existingEmpoyee = session.Query<Employee>()
                             .Where(e => e.Equals(newEmployee))
                             .FirstOrDefault();
if(existingEmployee != null)
   // Insert new employee to DB

答案 2 :(得分:0)

hmmmm。可能是它太多了,实际上 - 我希望“懒惰=额外”的ISet表现得像这样,但你可以编写自己的“额外懒惰”的ISet。 如果你没有遇到一个额外的惰性集合 - 它是一个集合,例如,当你问它的计数时,它不会获取所有内容,但会发出计数查询。 每当你尝试添加一些东西时,你额外的懒惰ISet都可以发出一个存在的查询。

如果你实现了这个,你的代码就会很干净,你可以将代码提交给nhibernate核心。

但是,您应该考虑添加范围,并注意不要发出N个查询

祝你好运。