2011-04-15 77 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