【问题标题】:NHibernate list saving and loading optimization adviceNHibernate 列表保存和加载优化建议
【发布时间】:2011-10-03 17:56:39
【问题描述】:

我有 3 个通过 NHibernate 映射的类:交叉路口、车辆和区域。

我的交叉点类包含属于交叉点的区域列表。我的 Vehicle 类包含 Vehicle 包含的区域列表。最后,我的 Zone 类包含一个包含 Zone 的 Vehicles 列表。

我的测试性能集包含 10,000 辆汽车、500 个区域和 250 个交叉路口。对于所有这些对象,我的 Zones 和 Vehicles 都映射了列表的加载时间大约是 27 分钟。

我不确定发生了什么,但这两个列表至少没有优化。如果我将 Vehicle 列表从 Zone 类映射中取出,则保存对象会有 10 分钟的差异。这似乎有点偏离了这两个列表是如何直接相关的。

似乎 NHibernate 正在递归地保存两个列表中的项目,并为保存过程增加了一堆开销。有什么方法可以优化这些列表以更快地保存对象?

这是我的 Intersection 和 Vehicle 都继承自的 Device 映射:

<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2">
  <class xmlns="urn:nhibernate-mapping-2.2" name="EMTRAC.Devices.Device, EMTRAC_v3, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" table="`Device`">
    <id name="PK" type="System.Int64, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
      <column name="PK" />
      <generator class="identity" />
    </id>
    <version name="LastModifiedOn" column="LastModifiedOn" type="timestamp" access="field.pascalcase-underscore" />
    <joined-subclass name="EMTRAC.Intersections.Intersection, EMTRAC_v3, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null">
      <key>
        <column name="Device_id" />
      </key>      
      <component name="Zones" access="property">
        <bag name="_list" cascade="all-delete-orphan" access="field" fetch="join" inverse="false">
          <key>
            <column name="Zone_PK" />
          </key> 
          <many-to-many class="EMTRAC.Zones.Zone, EMTRAC_v3, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"/>
        </bag>
      </component>
      <property name="ID" type="System.Int32, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
        <column name="ID" />
      </property>
    </joined-subclass>
    <joined-subclass name="EMTRAC.Vehicles.Vehicle, EMTRAC_v3, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null">
      <key>
        <column name="Device_id" />
      </key>      
      <component name="Zones" access="property">
        <bag name="_list" cascade="save-update" access="field" table="VehicleZones" inverse="true">
          <key>
            <column name="veh_id" not-null="true"/>
          </key>
          <many-to-many class="EMTRAC.Zones.Zone, EMTRAC_v3, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"/>
        </bag>
      </component>
      <property name="ID" type="System.Int32, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
        <column name="ID" />
      </property>           
    </joined-subclass>
  </class>
</hibernate-mapping>    

最后是我对 Zone 类的映射:

    <hibernate-mapping xmlns="urn:nhibernate-mapping-2.2">
  <class xmlns="urn:nhibernate-mapping-2.2" name="EMTRAC.Zones.Zone, EMTRAC_v3, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" table="`Zone`">
    <id name="ID" type="System.Int32, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
      <column name="PK"/>
      <generator class="identity" />
    </id>
    <version name="LastModifiedOn" column="LastModifiedOn" type="timestamp" access="field.pascalcase-underscore" />
    <property name="ID" type="System.Int32, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
      <column name="ID" />
    </property>    
    <component name="Vehicles" access="property">
      <bag name="_list" cascade="save-update" access="field" table="VehicleZones">
        <key>
          <column name="veh_id" not-null="true"/>
        </key>
        <many-to-many class="EMTRAC.Vehicles.Vehicle, EMTRAC_v3, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"/>
      </bag>
    </component>
  </class>
</hibernate-mapping>

任何想法如何提高这些列表的效率?现在它保存它们的每个表都包含 500,000 条记录,当然加载它们并保存这么多记录需要一些时间。

编辑

我删除了所有我忘记删除的lazy="false" 部分,它的速度大大提高了。我还对对象的保存和加载进行了一些调整,将部分分解为单独的线程以提高性能,并在 NHibernate 中实现事务的使用似乎有所帮助。

但是我遇到了一个障碍。我现在无法保存这两个列表。在将列表添加到车辆和区域之前,我已经预先保存了所有交叉路口、车辆和区域。但是,如果我在区域映射中包含车辆列表,我会在尝试更新车辆和列表时遇到错误。代码如下:

using (var tx = session.BeginTransaction())
{
    foreach (Vehicle vehicle in Program.data.Vehicles.list)
    {
        session.Update(vehicle);
    }
    tx.Commit();
}

我收到堆栈溢出异常。有什么想法吗?

你知道如何解决这个问题吗?

编辑 有没有办法将 Zone 类中的 Vehicles 列表和 Vehicle 类中的 Zones 列表映射到同一个表,这样它就不会递归地遍历这两个列表?这些列表相互关联。一个是车辆所属的区域列表,另一个是区域所属的车辆列表。在深入研究之后,我认为我没有正确映射。

编辑 我一直在使用我相信的正确映射取得进展。我发布了一个更简化版本的 Device 映射,它映射了 Intersection 和 Vehicle 类,以及一个简化版本的 Zone,它当然映射了 Zone 类。

这似乎保存得比较好,但有一个例外。出于某种原因,保存大量项目会抛出 Stack Overflow 异常。这是我正在使用的方法:

//  Create a Transaction for batch updating
using (var tx = session.BeginTransaction())
{
    foreach (Vehicle veh in Program.data.Vehicles.list)
    {
        session.Save(veh);
    }
    //  Commit transactions
    tx.Commit();
}

这适用于 250 个十字路口、500 个区域和 1000 辆车。但是,我试图创建一个测试数据集以在更极端的环境中强调这一点,并且在尝试将测试数据保存到数据库通过这种方式。

有什么想法吗?

【问题讨论】:

    标签: nhibernate nhibernate-mapping


    【解决方案1】:

    虽然不是答案,但我建议 NHibernate 不是保存 500K 记录的最佳解决方案,尽管我承认有时很难用(例如)存储过程替换那段代码。

    您可以考虑使用无状态会话,因为它针对速度进行了优化,但不跟踪对象更改。另一件事是找出正在执行的 SQL。您是否看到了意想不到的更新?在这种情况下,您的映射文件可能有错误。

    最后,如果您进行了大量数据更改,那么 NH 需要将它们刷新到数据库,并且它需要跟踪加载的对象以跟踪这些更改。在 500K 条记录中,需要保存在内存中的数据非常多。

    【讨论】:

    • 我可以在保存所有项目时处理长时间保存,因为整个集合不会经常保存,我第一次可以处理 20 分钟的保存。但是我不能接受的是它需要 6-8 分钟来加载 b/c 它似乎递归地加载列表中的所有这些项目。那是我的大问题。这更像是一个概念证明,它开始看起来好像不像我想要的那样可用。
    • 嗯。在这种情况下,我会查看您的映射获取策略。例如,您需要 fetch="join" 或 Lazy="false" 吗?我猜你知道,这将迫使 NH 提前加载数据,但这真的需要吗?你能用批量大小代替吗?当我们分析生成的 SQL 并找出原因时,我们的大多数 NH 性能问题都得到了解决......尽管您可能仍然会因为尝试加载到内存中的对象数量而遇到问题。
    • 删除了我忘记删除的附加lazy="false" 属性,这对我的新保存策略以及我的新保存策略都有很大的改进。知道保存 2 个列表的最佳方法是什么吗?我可以告诉 NHibernate 只保存对象的特定属性吗?
    • 如果您使用 StatelessSession,您将如何保存 2 个类中包含的列表?我不断收到持久性不存在错误。
    【解决方案2】:

    NHibernate 似乎递归地保存了两个 列表

    你不必猜测; nhibernate 有great logging mechanisms 可以准确地告诉你生成了什么sql。看看它到底做了什么并将其发回此处进行分析可能是一个好主意。

    其次,nHib 并非真正适用于此类数据量。
    查看我收到的建议here
    我能给你的最好建议和我收到的一样——不要一次将 10,000 个对象加载到内存中!无论您使用哪种数据访问方法,这都不是一个好习惯。

    【讨论】:

    • 我最大的问题是加载区域中的车辆列表。如果我保存两个列表,那么它会加载所有项目两次,这实际上是不需要的,因为这两个列表包含相同的数据。但是,如果我只是将列表保存在车辆中,我可以通过在车辆区域列表中执行 foreach Vehicle 后跟 foreach 区域并将车辆添加到区域的车辆列表中来构建区域列表。但是,这会破坏延迟加载,因为我正在触摸每个车辆的区域列表以执行此操作,因此这相当于不延迟加载列表,效率极低。
    • 更改我添加的第一条评论。对于我的应用程序,我有必要一次延迟加载那么多对象。不一定要加载那么多对象。我的问题是这些列表是递归保存的,如果我只保存一个,从我保存的列表访问列表会将延迟加载吹出窗口,我目前真的没有解决方案除了完全抛弃 NHibernate 之外,请注意。
    【解决方案3】:

    实际上是递归地保存它们 b/c 我的映射不正确。我需要一个反向关系,然后我需要修改映射以指向外键。最后我需要一个 Modified 字段来告诉 NHibernate 该项目已被修改并且需要更新。

    这是正确的映射:

    车辆:

    <hibernate-mapping xmlns="urn:nhibernate-mapping-2.2">
      <class xmlns="urn:nhibernate-mapping-2.2" name="EMTRAC.Devices.Device, EMTRAC_v3, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" table="`Device`">
        <id name="PK" type="System.Int64, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
          <column name="PK" />
          <generator class="identity" />
        </id>
        <version name="LastModifiedOn" column="LastModifiedOn" type="timestamp" access="field.pascalcase-underscore" />
        <joined-subclass name="EMTRAC.Vehicles.Vehicle, EMTRAC_v3, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null">
          <key>
            <column name="Device_id" />
          </key>      
          <component name="Zones" access="property">
            <bag name="_list" cascade="save-update" access="field" table="VehicleZones" inverse="true">
              <key>
                <column name="veh_id" not-null="true"/>
              </key>
              <many-to-many class="EMTRAC.Zones.Zone, EMTRAC_v3, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"/>
            </bag>
          </component>
          <property name="ID" type="System.Int32, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
            <column name="ID" />
          </property>     
      </class>
    </hibernate-mapping>
    

    区域:

    <hibernate-mapping xmlns="urn:nhibernate-mapping-2.2">
      <class xmlns="urn:nhibernate-mapping-2.2" name="EMTRAC.Zones.Zone, EMTRAC_v3, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" table="`Zone`">
        <id name="ID" type="System.Int32, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
          <column name="PK"/>
          <generator class="identity" />
        </id>
        <version name="LastModifiedOn" column="LastModifiedOn" type="timestamp" access="field.pascalcase-underscore" />
        <property name="ID" type="System.Int32, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
          <column name="ID" />
        </property>    
        <component name="Vehicles" access="property">
          <bag name="_list" cascade="save-update" access="field" table="VehicleZones">
            <key>
              <column name="veh_id" not-null="true"/>
            </key>
            <many-to-many class="EMTRAC.Vehicles.Vehicle, EMTRAC_v3, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"/>
          </bag>
        </component>
      </class>
    </hibernate-mapping>
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2018-09-05
      • 1970-01-01
      • 2015-06-03
      • 2020-04-14
      • 1970-01-01
      • 1970-01-01
      • 2019-06-07
      • 1970-01-01
      相关资源
      最近更新 更多