【问题标题】:Hibernate Mass Insert/update : is this a good approach?Hibernate Mass Insert/update:这是一个好方法吗?
【发布时间】:2012-08-21 08:25:11
【问题描述】:

我目前正在我们基于休眠的应用程序中进行性能和内存调整,以进行大批量/批量导入。 我们基本上是在导入一个包含产品数据的 CSV 文件,其中一些产品是新的(插入),一些是存在的(更新)。

我现在的重点是选择一种策略来找出要更新哪些实体以及要插入哪些实体,而无需对 CSV 文件中的每一行进行检查(如果存在则选择)。

我目前的做法是这样的:

  1. 构建数据库中所有对象的哈希图。
  2. 遍历 CSV 并使用 hashmap 来决定是更新还是插入。

这种方法效果很好,测试证明它比对每一行进行这样的单个 IF EXISTS 检查要快很多。

如果数据库中有很多实体,我担心的是内存大小。

现在我考虑使用上述方法的轻微变化,我想知道意见。 基本上我想做的是对多行进行多批 IF EXISTS 检查(例如 SELECT FROM table where sku IN (sku1, sku2, sku3)

这是一些伪代码:

1. Database contains: db{sku1, sku2,sku3,sku5}

2. file contains: file {sku1, sku2, sku3, sku6}

3. Expected result: 
   updates: {sku1, sku2, sku3}
   inserts{sku6}

4. Algorithm

   have a map to keep database entities which need updates
   updatemap {}
   now iterate over the file in e.g. batches of 2 rows (for demo purposes)
   1st iteration: foreach (select where sku IN (sku1, sku2) limit 2) as elem
    -> updatemap.add(elem)  -> elem is asumed to be a persistent entity here
    -> myDAO.update(elem)   -> executes Spring's getHibernateTemplate().update() under the hood

   -> updatemap contents after 1st loop {sku1, sku2}

   2nd iteration: foreach (select where sku IN (sku3, sku6) limit) 2 as elem
    -> updatemap.add(elem)    
    -> myDAO.update(elem)

   -> updatemap contents after 3nd loop {sku1, sku2, sku3}

顺便说一句:我也已经假设像(if i % 30 == 0) session.flush; session.clear();这样的东西

现在我们知道所有已更新的元素。所有不在 updatemap 中的 skus 基本上都是插入,我们可以使用简单的集合算术来确定这些

文件 {sku1, sku2, sku3, sku6} - updatemap {sku1, sku2, sku3} = newinserts {sku6}

现在我们可以继续插入剩余的 CSV 行。

结论 我的假设是,由于文件内容的分块,我可以限制使用的内存量。与最初的方法相比,我有更多的 SELECT 语句,但如果数据库中已经有数千个实体,我可以更好地控制内存使用。

您对此有何想法? 还有哪些其他有效的方法可以找出要更新哪些实体以及批量插入哪些实体?

【问题讨论】:

    标签: java sql hibernate jdbc


    【解决方案1】:

    我遇到了完全相同的问题,涉及数百万条记录,并且与您几乎完全一样地解决了它。对旁观者来说可能不明显的一个限制是,我们不能使用常规的 Hibernate 方式的 load-mutate-update,因为这会产生过多的冗余流量。

    仔细阅读后,我的方法与您的方法不同,因为除了处理单个块之外,我不保留任何信息。在进行下一个之前,我会完整处理该块,包括所有插入和更新。只有这样,您才能获得可扩展的解决方案。

    对我来说最薄弱的一点是使用executeUpdate,它不会使用JDBC 批处理API。我计划进行自定义实现,但对于我的特定用例,我不需要为每个块使用多个 executeUpdate

    【讨论】:

    • 感谢您的回答。我的示例中的 executeUpdate 并不是 hibernate 的 executeUpdate。我将示例修改为“myDAO.update(elem)”。我的假设是,如果我启用“hibernate.jdbc.batch_size=30”、“hibernate.order_inserts=true”和“hibernate.order_updates=true”并在connectionURL上设置“rewriteBatchedStatements=true”,这应该使用批处理功能.这就是你所说的“最弱点”吗?
    • 嗯。 myDAO.update 在 Hibernate 方面是如何实现的?如果您已经有一个持久的elem,这意味着它来自数据库,所以它被不必要地加载了。如果不是,那么您必须将merge 加入会话,然后update,这将导致相同的结果。无论如何,你通过 Hibernate 的实体状态管理来做这件事,它会导致从 DB 到你的不必要的流量。
    • Yesm 在我的“myDAO.update(elem)”中的原始问题中,由于“选择 sku IN (sku1, sku2) 限制 2”的结果,elem 将是一个持久实体。至少这是我认为有意义的,因为我想更新已经在数据库中的实体,因此我需要加载它。您能否详细说明您的方法?您是否指的是直接普通的 SQL/JDBC 'UPDATE table_of_elem SET fields=vals WHERE sku='sku1' 等,而不通过休眠获取实体?
    • 我只选择了 ID,我认为您也这样做了。然后我使用executeUpdate,即立即执行UPDATE。我不需要知道旧值来编写新值。如果我这样做,流量会翻倍。
    • 请注意,您可能会面临executeUpdate 的低效率,因为它不使用批处理。我的想法是制作一些自定义代码来执行原始 JDBC 批量更新。
    【解决方案2】:

    我的想法

    1) 当你这样做时 SELECT FROM table where sku IN (sku1, sku2, sku3) )

    当未找到 sku 时,每个查询都可能进行全表扫描,如果您对 n 次通过中的剩余实体执行此操作,最坏的情况可能需要 n * 表扫描。

    也许更简单的方法是为 csv 中的所有实体创建一个重复的表(可能只有一列用于 skus 并执行 MINUS 以插入新的 skus)

     select sku from dup_table
      MINUS  //(EXCEPT for Mysql)
     select sku from table`
    

    您可以将这些记录保存到新表 (dup_table2) 中并在 dup_table 上执行另一个 MINUS 将更新 sku。但是这些运算符是特定于数据库的,我不确定看到了多少性能提升。但恕我直言,看起来比 where in 子句更好(尤其是当 csv 列表变大时)

    【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2013-11-05
    • 2012-07-20
    • 2010-12-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多