2011-04-15 71 views
2

nHibernate3;从EAV数据模式中检索4xxx记录。当nHibernate或.NET首次初始化这些集合时,我们看到了严重的处罚。随后的调用看起来更有效。在SQL Server Management Studio中运行相同的查询会导致预期的快速返回时间。如何解决不好的nHibernate集合初始化问题

使用Fluent和运行时映射代替.hbm.xml;好奇,如果序列化映射会在这里帮助?

nHibernate事件探查器和log4net日志记录似乎并没有给我太多的帮助。在这个过程中,总共有140,000个实体在进行水合。

附上我dotTrace性能跟踪的截图,显示了集合初始化处罚: dotTrace of slow nHibernate collection initialization

是否尝试过加入,渴望fetchtypes,没有明显的效果,但我不是100%肯定,我实现这些正确的 - 不只是父母需要如此指定,还是需要标记子女表?

var products = ((HandleSession)_handleSession).Session.CreateCriteria(typeof(Product)) 
        .SetFetchMode("Product", FetchMode.Eager) 
        .List<Product>() 
        .AsEnumerable(); 

具备通过web.config中启用反射优化器(我认为): With reflection optimizer enabled

这是大多数时间都花在:

return new ProductList(products.Select(p => p.ToProductContract())); 

这简直是一个扩展方法这样做:

public static ProductContract ToProductContract(this Product product) 
     { 
      return new ProductContract 
         { 
          Name = product.ProductName, 
          ProductTypeName = product.ProductType.ProductTypeName, 
          UpdateTimeStamp = product.UpdateDateTime, 
          ProductNumber = product.ProductNumber, 
          Attributes = product.ProductAttributes.ToCommonAttribute().ToList(), 
          GroupCategories = product.ProductGroups.ToGroupCategory().ToList(), 
          PublicUniqueId = product.PublicUniqueId 
         }; 
     } 

映射:

internal class ProductMapping : ClassMap<Product> 
    { 
     private const string _iscurrentindicator = "IsCurrentIndicator=1"; 

     public ProductMapping() 
     { 
      Table("Product"); 
      Id(Reveal.Member<Product>("ProductId")).GeneratedBy.Identity().Column("ProductID"); 
      Map(x => x.ProductNumber).Column("ProductNumber").Not.Nullable(); 
      Map(x => x.ProductName).Column("ProductName").Not.Nullable(); 
      Map(x => x.InsertDateTime).Column("InsertedDateTime").Nullable().ReadOnly(); 
      Map(x => x.UpdateDateTime).Column("UpdatedDateTime").Nullable(); 
      Map(x => x.PublicUniqueId).Column("ProductGUID").Generated.Insert(); 

      References(x => x.ProductType).Column("ProductTypeId").Not.Nullable(); 
      HasMany(x => x.ProductAttributes) 
       .KeyColumn("ProductId") 
       .Inverse() 
       .Fetch 
       .Subselect() 
       .Where(_iscurrentindicator) 
       .Cascade 
       .SaveUpdate(); 

      HasMany(x => x.ProductGroups).KeyColumn("ProductId").Fetch.Subselect().Where(_iscurrentindicator); 
      DynamicUpdate(); 
      DynamicInsert(); 
      BatchSize(500); 
     } 
    } 

internal class ProductGroupMapping : ClassMap<ProductGroup> 
    { 
     public ProductGroupMapping() 
     { 
      Table("ProductGroup"); 
      Id(x => x.ProductGroupId).Column("ProductGroupId").GeneratedBy.Identity(); 
      References(x => x.Product).Column("ProductId").Not.Nullable(); 
      References(x => x.Group).Column("GroupId").Not.Nullable(); 
      //Where("IsCurrentIndicator=1"); 
     } 
    } 

internal class ProductAttributeMapping : ClassMap<ProductAttribute> 
    { 
     public ProductAttributeMapping() 
     { 
      Table("ProductAttribute"); 
      LazyLoad(); 
      Id(x => x.ProductAttributeId).GeneratedBy.Identity().Column("ProductAttributeID"); 
      References(x => x.Product).Column("ProductID").Not.Nullable(); 
      References(x => x.Attribute).Column("AttributeID").Not.Nullable().Fetch.Join(); 
      Map(x => x.PositionNumber).Column("PositionNumber").Nullable(); 
      Map(x => x.ValueText).Column("ValueText").Nullable(); 
      Map(x => x.ValueBinary).Column("ValueBinary").Nullable(); 

      Component(x => x.OperationalAuditHistory, m => 
         { 
          Table("ProductAttribute"); 
          m.Map(x => x.ExpirationDateTime).Column("ExpirationDateTime").Nullable(); 
          m.Map(x => x.IsCurrent).Column("IsCurrentIndicator").Not.Nullable(); 
          m.Map(x => x.OperationCode).Column("OperationCode").Nullable(); 
          m.Map(x => x.OperationDateTime).Column("OperationDateTime").Nullable(); 
          m.Map(x => x.OperationSystemName).Column("OperationSystemName").Nullable(); 
          m.Map(x => x.OperationUserName).Column("OperationUserName").Nullable(); 
          m.Map(x => x.LastUserPriority).Column("LastUserPriority").Nullable(); 
         }); 

      DynamicInsert(); 
      BatchSize(50); 
     } 
    } 

不幸的是,未来我仍然会得到类似的结果。这是一个新的痕迹;目前,我已经转向Release和x64的关键项目,所以时间较短,但比例仍然几乎相同;以及与.Eager:

var products = ((HandleSession) _handleSession).Session.CreateCriteria(typeof (Product)) 
        .SetFetchMode("ProductAttribute", FetchMode.Join) 
        .SetFetchMode("ProductGroup", FetchMode.Join) 
        .SetFetchMode("ProductType", FetchMode.Join) 
        .Future<Product>() 
        .AsEnumerable(); 

dotTrace - Release mode, targeting x64, with .Future()

与.Eager和。未来生成的SQL到位:

SELECT this_.ProductID为 ProductID0_1_,this_.ProductNumber为 ProductN2_0_1_ ,this_.ProductName as ProductN3_0_1_,this_.InsertedDateTime as Inserted4_0_1_, this_.UpdatedDateTime as UpdatedD5 _0_1_,this_.ProductGUID如 ProductG6_0_1_,this_.ProductTypeId如 ProductT7_0_1_, producttyp2_.ProductTypeID如 ProductT1_6_0_, producttyp2_.ProductTypeName如 ProductT2_6_0_从产品THIS_ 内连接上ProductType producttyp2_ this_.ProductTypeId = producttyp2_.ProductTypeID;

SELECT productatt0_.ProductId如 ProductId2_, productatt0_.ProductAttributeID如 ProductA1_2_, productatt0_。ProductAttributeID为 ProductA1_2_1_, productatt0_.PositionNumber为 Position2_2_1_,productatt0_.ValueText 为ValueText2_1_, productatt0_.ValueBinary为 ValueBin4_2_1_,productatt0_.ProductID 为ProductID2_1_, productatt0_.AttributeID为 Attribut6_2_1_, productatt0_.ExpirationDateTime为 Expirati7_2_1_ , productatt0_.IsCurrentIndicator如 IsCurren8_2_1_, productatt0_.OperationCode如 Operatio9_2_1_, productatt0_.OperationDateTime如 Operati10_2_1_, productatt0_.OperationSystemName如 Operati11_2_1_, productatt0_.OperationUserName如 Operati12_2_1_, productatt0_.LastUserPriority如 LastUse13_2_1_, attribute1_.AttributeId如 Attribut1_1_​​0_, attribute1_.AttributeName如 Attribut2_1_0_, attribute1_.DisplayName如 DisplayN3_1_0_, attribute1_ .DataTypeName as DataType4_1_0_, attribute1_.ConstraintText as Constrai5_1_0_, attribute1_.ConstraintMin as Constrai6_1_0_, attribute1_.ConstraintMax如 Constrai7_1_0_,attribute1_.ValuesMin 如ValuesMin1_0_, attribute1_.ValuesMax如 ValuesMax1_0_,attribute1_.Precision 如Precision1_0_ FROM ProductAttribute productatt0_内上 productatt0_.AttributeID = attribute1_.AttributeId WHERE 加入属性 attribute1_ (productatt0_.IsCurrentIndicator = 1) 和productatt0_.ProductId在(选择 this_.ProductID从产品THIS_ 内上 this_.ProductTypeId = producttyp2_.ProductTypeID加入ProductType producttyp2_)

SELECT productgro0_.ProductId如 ProductId1_, productgro0_.ProductGroupId如 ProductG1_1_, productgro0_.ProductGroupId如 ProductG1_3_0_,productgro0_.ProductId 如ProductId3_0_,productgro0_.GroupId 如GroupId3_0_ FROM ProductGroup productgro0_ WHERE (productgro0_.IsCurrentIndicator = 1) 和productgro0_.ProductId在(选择 this_.ProductID从产品上 this_.ProductTypeId = producttyp2_.ProductTypeID)

THIS_ 内部联接ProductType producttyp2_
+0

尝试 – driushkin 2011-04-15 21:52:40

+0

转向反射优化器是不熟悉的是,将采取一看,谢谢 - 你碰巧有代码它,而不是我似乎通过谷歌找到的XML配置? – andrewbadera 2011-04-15 21:58:11

+0

通过web.config启用反射优化添加屏幕截图(我认为)。 – andrewbadera 2011-04-15 22:10:18

回答

8

1)序列化映射只会帮助减少构建SessionFactory所需的时间。如果上述查询不是第一次访问数据库,那么在这方面不会完成任何事情。

2)设置FetchMode需要被应用到孩子,像这样:

var products = ((HandleSession)_handleSession).Session.CreateCriteria(typeof(Product)) 
       .SetFetchMode("ProductChildren", FetchMode.Eager) 
       .List<Product>() 
       .AsEnumerable(); 

3)这看起来像一个N + 1层的问题,如果我正确地解释了屏幕截图的方法。您是否将查询结果中的Products转换为ProductDTO列表?如果是这样,看起来好像子集合在循环内从数据库延迟加载。

编辑:

为了打击N + 1选择,我们将不得不告诉NHibernate的预先加载的一切,最好用期货。这里有一个潜在的解决方案,它基本上用一些Select语句从db中获取所有数据。我没有包含任何条件。那些你必须相应地添加。

// any where-condition will have to be applied here and in the subsequent queries 
var products = session.QueryOver<Product>() 
    .Future(); 

var products2 = session.QueryOver<Product>() 
    .Fetch(p => p.ProductType).Eager 
    .Future(); 

var products3 = session.QueryOver<Product>() 
    .Fetch(p => p.ProductAttributes).Eager 
    .Future(); 

var products4 = session.QueryOver<Product>() 
    .Fetch(p => p.ProductGroups).Eager 
    .Future(); 

// Here we execute all of the above queries in one roundtrip. 
// Since we already have all the data we could possibly want, there is no need 
// for a N+1 Select. 
return new ProductList(products.Select(p => p.ToProductContract())); 
+0

到目前为止,它是我第一次访问数据库;我刚刚参加了这个项目,所以我没有机会真正充实测试场景,我不确定这是否是第一次访问问题,我会发现。感谢, – andrewbadera 2011-04-16 19:56:17

+0

将尝试与获取模式。 – andrewbadera 2011-04-16 19:56:38

+0

是的,我相信这实际上是ToProductList中正在做的事情。我将在星期一进行核实。 – andrewbadera 2011-04-16 19:57:03

1

一个选项是对您的集合启用批量大小。我假设这些是懒惰的,并且启用了批量大小,它会尝试在一次往返中为多个实体提取集合。

如果您使用一个集合获取1个实体,但没有区别,但是如果选择1000个实体都具有一个集合,则会产生巨大差异。使用会导致2个查询,而不是1001

试过的1000批次大小来找到一些文档,但只找到这个例子:

nhibernate alternates batch size

使用你的情况会导致连接策略巨大的结果集,所以这不是一个好的选择。一个更好的选择是使用FetchMode.Select,它将明确强制你的集合在后续的往返中加载。

,可以提高性能的另一件事是设置:

Session.FlushMode = FlushMode.Never; 

哪些禁用范围的自动冲洗。如果您实际上所做的只是读取数据,而不是修改它,这会非常有用。但是,您会看到调用IsDirty或对您的调用堆栈中脏对象的任何其他检查。

+0

批量大小已启用;它是50,我也尝试过500,行为没有改变。 – andrewbadera 2011-04-18 15:59:22

+0

此外,这里只有两个查询 - 这是所有时间都在进行的收集初始化和人口统计。那么,以及MVC引擎呈现结果。 – andrewbadera 2011-04-18 15:59:49

+0

我的经验是,当你达到相当数量的对象时,NHibernate在将结果集中的数据转换成实体时相当慢。根据截图,你处理的结果大到100 000行,这是正确的吗? – jishi 2011-04-18 16:09:43