【问题标题】:Fetching millions of records in java [closed]在java中获取数百万条记录[关闭]
【发布时间】:2014-05-24 18:20:41
【问题描述】:

非常开放的问题, 我需要编写一个从 Oracle 数据库读取数百万条记录(比如说帐户信息)的 Java 客户端。将其转储为 XML 并通过 Web 服务发送给供应商。

最优化的方法是什么?从获取数百万条记录开始。我走的是 JPA/hibernate 路线,在获取 200 万条记录时遇到了 outofMemory 错误。

JDBC 是更好的方法吗?获取每一行并构建XML?还有其他选择吗?

我不是 Java 专家,因此不胜感激。

【问题讨论】:

  • 百万可能没有你想象的那么大。
  • 如果您在 oracle 内部创建了一个存储过程来生成 XML 文件会怎样?然后,您可以从您的 java 应用程序调用存储过程,并在写入后发送 XML 文件。很多时候最好让数据库本身来完成繁重的数据提升。
  • 是否必须是通过 Web 服务发送给供应商的单个 XML 文档?是否可以选择“分块”(将其分解为较小的文件,每个文件包含 100K 行)?
  • @Coffee - 这取决于记录的平均大小,不是吗?如果每条记录平均为 1KB,则为几 GB。如果 OP 正在尝试构建 XML 的内存中 DOM 表示,那么难怪会出现内存问题。
  • 采用 SAX 方法来构建您的 XML,您的方法将扩展到任意大量记录,仅受 1. 磁盘空间和 2. 您可以将表/数据库锁定多长时间 (如果预期并发更新),即。假设您正在将 XML 写入输出流。您一次加载到内存中的记录数应该被参数化。

标签: java hibernate jdbc


【解决方案1】:

我们曾经遇到过类似的问题,我们的记录大小超过了 2M。我们就是这样接近的。

  • 简单地排除使用任何 OR 映射工具,因为如果要将数据转储到 XML,则基本上不需要创建大型 POJO 等大量开销。

  • 普通 JDBC 是要走的路。这样做的主要优点是它返回了一个 ResultSet 对象,该对象实际上并不一次包含所有结果。因此解决了在内存中加载整个数据的问题。数据在我们迭代 ResultSet

  • 时加载
  • 接下来是创建 XML 文件。我们创建一个 XML 文件并在Append mode 中打开。

  • 现在在循环中迭代Resultset 对象,我们创建 XML 片段,然后将其附加到 XML 文件中。这一直持续到整个 Resultset 被迭代。

  • 最后我们拥有的是XML文件将所有的记录。

  • 现在,为了共享此文件,我们创建了一个 Web 服务,如果该文件可用,它将返回此 XML 文件(存档/压缩)的 URL。

  • 此后,客户端可以随时下载此文件。

  • 请注意,这不是一个同步系统,这意味着该文件在客户端进行调用后不可用。由于创建 XML 调用需要大量时间,因此 HTTP 通常会超时,因此采用这种方法。

只是一种您可以从中获取线索的方法。希望这会有所帮助。

【讨论】:

  • 我已经成功地将 Hibernate 用于大型结果。不涉及大型 POJO。
  • 是的。如果数据要以 XML 形式转储,并且不受 POJO 促进的任何业务操作的影响,我更关心的是这种必要性。
  • @Santosh 关于您对我们的 ORM 的裁决:我不同意,尤其是如果您在同一域模型上使用 JAXb 来创建 XML。 关于 JDBC 优于 JPA 的理由:您可以(并且应该)在 JPQL/CriteriaQuery 中对结果进行分页,从而防止将超过合理大小的批次加载到内存中。
  • @okiharaherbst,我对将数据转储到文件的特定用例的观察。 JPA 没有提供任何优于 JDBC 的显着优势(在这种特殊情况下)。考虑效率/资源消耗/简单的观点。
  • @Santosh 可以说,直接低级 jdbc 是否比 jpa 提供任何优势,反之亦然。我声称应该更频繁地在效率/资源上进行权衡,因为垂直扩展节点现在比高技能劳动力便宜。在这方面,我宁愿在我负担得起的情况下采用摆弄 SQL 的高级方法,并让我的大脑处于更系统的思维模式——我相信这也是值得鼓励的整个 Java
【解决方案2】:

使用ResultSet#setFetchSize() 优化从数据库中获取的记录。

What does Statement.setFetchSize(nSize) method really do in SQL Server JDBC driver?

在 JDBC 中,ResultSet#setFetchSize(int) 方法对于 JVM 内的性能和内存管理,因为它控制 从 JVM 到数据库的网络调用次数和 相应地用于ResultSet 处理的 RAM 量。

在这里阅读Oracle ResultSet Fetch Size

【讨论】:

    【解决方案3】:

    对于这种大小的数据,您可能可以使用更多内存来启动 java。启动 Java 时使用 -Xmx-Xms 进行检查。

    如果您的数据确实太大而无法放入内存,但又不足以保证对不同技术的投资,请考虑分块操作。这必须一次完成吗?你能把数据分成 10 个块并独立处理每个块吗?如果必须一次性完成,您是否可以从数据库中流式传输数据,然后将其流式传输到文件中,而忘记您已完成的事情(以保持 JVM 中的内存使用率较低)?

    【讨论】:

    【解决方案4】:
    1. 按前面的答案解释,分块读取记录。

    2. 使用 StAX http://stax.codehaus.org/ 将记录块流式传输到您的 XML 文件,而不是将所有记录流式传输到一个大文档中

    【讨论】:

      【解决方案5】:

      就 Hibernate 方面而言,使用SELECT 查询(而不是FROM 查询)获取以防止填满缓存;或者使用statelessSession。还要确保使用scroll() 而不是list()。还建议将hibernate.jdbc.fetch_size 配置为 200 之类的值。

      在响应方面,XML 是一个非常糟糕的选择,因为解析很困难。如果已设置,请确保使用流式 XML 序列化程序。例如,XPP3 库包含一个。

      【讨论】:

        【解决方案6】:

        虽然合理的 Java 方法可能会涉及到 XML 的 StAX 构造以及分页结果集(直接 JDBC 或 JPA),但请记住,您可能需要一直锁定数据库以进行更新,这可能会或可能会在你的情况下是不可接受的。

        我们采用了一种不同的、以数据库为中心的方法,在 INSERTUPDATE 上使用 存储过程触发器 来生成与每一行对应的 XML 节点/[块]数据。这不断确保 250GB 以上的原始数据及其 XML 表示(约 10GB)是最新的,并将导出(没有双关语)减少为单纯的连接问题。

        【讨论】:

          【解决方案7】:

          你仍然可以使用 Hibernate 来获取数百万的数据,只是你不能在一轮中完成,因为数百万是一个很大的数字,当然你会有内存不足的异常。您可以将其分成页面,然后每次转储到 XML,这样记录就不会保存在 RAM 中,您的程序也不需要这么大的内存。

          在我之前的项目中,我经常使用这两种方法。不幸的是,我不太喜欢使用 HQL,所以我没有相应的代码。

          所以这里INT_PAGE_SIZE 是您希望每轮获取的行数,getPageCount 是获取要获取所有记录的总轮数。 那么paging就是按页取记录,从1到getPageCount

          public int getPageCount(Criteria criteria) {
              ProjectionList pl = Projections.projectionList();
              pl.add(Projections.rowCount());
              criteria.setProjection(pl);
              int rowCount = (Integer) criteria.list().get(0);
              criteria.setProjection(null);
              if (rowCount % INT_PAGE_SIZE == 0) {
                  return rowCount / INT_PAGE_SIZE;
              }
              return rowCount / INT_PAGE_SIZE + 1;
          }
          
          public Criteria paging(Criteria criteria, int page) {
              if (page != -1) {
                  criteria.setFirstResult((page - 1) * INT_PAGE_SIZE);
                  criteria.setMaxResults(INT_PAGE_SIZE);
              }
              return criteria;
          }
          

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 2015-12-14
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2016-03-06
            相关资源
            最近更新 更多