【问题标题】:ColdFusion memory spike when reading csv file for DB import读取用于数据库导入的 csv 文件时,ColdFusion 内存峰值
【发布时间】:2011-12-29 20:23:48
【问题描述】:

我们有一个 ColdFusion 9 脚本,它会定期运行,读取 CSV 文件并将记录插入到 Oracle 11g 数据库中。 CSV 文件大约有 50 列,其中 8 列由 CF 使用(CSV 格式不能修改)。 CFM的一般流程是:

  1. 将文件读入变量
  2. CFLOOP 使用该变量作为列表属性,并以 CHR(13) 作为分隔符
  3. 调用 Oracle 存储过程,使用 ListGetAt 从文件中插入各种值

存储过程执行以下操作:

  1. 将具有 2 个字段的记录插入到表 1 中
  2. 在表 2 中插入一条有 8 个字段的记录(包括表 1 的主键)
  3. 不返回任何内容

这在大多数情况下都能成功运行,只需几秒钟即可读取包含数百条记录的 400 KB 文件。但是,有时我们会得到一个大容量并最终得到一个 13k 记录的 5MB 文件。当我们尝试处理这么大的文件时,我观察到 JVM 内存使用量在 10-15 秒内从 90MB 上升到大约 680MB,之后 CF 服务器监视器停止响应(就像 CF 一样),迫使我们重新启动服务。日志报告 JVM 内存不足错误:

"错误","qtp4795249-38798","12/28/11","16:29:20","超出 GC 开销限制" java.lang.OutOfMemoryError: 超出 GC 开销限制

我们的 JVM 堆大小目前为 768MB。我没有尝试增加它,因为即使这确实解决了这个问题,它也不能在未来保护我们,并且服务器的其余正常负载几乎不需要那么多。而且对于需要重启才能在生产机器上生效的 JVM 设置,我犹豫不决。

这很难测试,因为导入过程运行良好,在我的本地开发机器和我们的 QA 机器上几乎没有任何明显的内存负载,但它们与数据库的连接速度要慢得多,需要 10-15 分钟完成。

我会很感激任何想法,尤其是关于记忆去向的想法。我无法弄清楚 5MB 的数据是如何变成 700 MB 的数据的。我们确实打开了调试信息,但调用脚本的 IP 地址不在调试列表中,我使用 cfsetting 标签关闭了此页面的调试。以前有一个步骤 1.5 将 CSV 数据转换为 ColdFusion 查询,但我为了提高效率而取消了它。两种方式都会导致 oom 错误。

【问题讨论】:

  • 感谢所有回复。由于客户的月末和年终处理,以及他们昨天将 CF 服务器关闭 30 秒的响应,我们将等待一周左右,然后再尝试实施或测试任何这些建议。我会更新这个问题。

标签: csv coldfusion


【解决方案1】:

您是否考虑过直接导入数据库?对于 MySQL,这是LOAD DATA INFILE,对于 SQL Server,它是 BULK INSERT。如果您需要一些额外的处理,则可能的方法是将数据加载到临时表中,然后使用 CFML 进行处理,这可以轻松地批量完成以进行繁重的处理。

【讨论】:

  • 感谢您的建议。我们确实考虑过这一点,但我们的数据库人员目前关系很好,管理层希望在尝试从头开始编写新的数据库导入之前尝试在 ColdFusion 中快速解决这个问题。
【解决方案2】:

与其在开始处理之前将整个文件读入内存,不如通过一次读取一行来循环文件内容。 Ben Nadel(当然)有一篇很好的博客文章讨论了这种技术:

Reading In File Data One Line At A Time Using ColdFusion's CFLoop Tag Or Java's LineNumberReader

另见livedocs for cfloop,特别是文件属性:

【讨论】:

  • 这些想法很好。我会调查这些并报告。谢谢。
  • Ben 有一些好东西,但是让某人离开这个网站以获得他们的答案并不是那么好。你能至少解释一下 Ben 说的话吗?
  • 我的建议不过是 cfloop file="foo.txt"。我还应该包括什么?
【解决方案3】:

我们有一个 CF 应用程序,它可以导入房地产 MLS 列表并遇到类似问题。我们使用的核心文件是 100MB,读取它并立即循环它会产生很多问题。我们最终做了几件事:

  1. 将文件分割成块。导入过程使用 cygwin 中的 split.exe 实用程序将文件拆分为 4,000 个行块。然后,我们使用 CFDIRECTORY 获取块列表并一次处理一个。

  2. 对于每个块,我们将其读入,然后将文件内容拆分为一个数组(使用 listToArray() 和 chr(13) 作为分隔符)。

  3. 我们从 1 循环到 arrayLen(chunkArray) 而不是直接循环文件内容。这样做更多是为了速度。在该循环中,我们还将每一行分成一个数组。我们发现这样做并以 thisRow[i] 的形式访问值(其中 i 是文件中的列号)比重复调用 listGetAt() 快得多。我们要导入的文件有 90 多列。

  4. 我们增加了 JVM 内存限额。我们的服务器很忙,这增加了一些开销。我们最终将 JVM 推到了 32 位服务器(大约 2GB)的高度,以便在需要时可以使用内存。

【讨论】:

    【解决方案4】:

    还要注意不要循环查询。花费在执行查询上的大部分时间只是建立数据库连接。

    每当我必须从一个文件中进行多次这样的插入时,我都会创建每个 SQL 插入语句并将它们保存在一个由分号分隔的变量中。然后,我每 100 条语句一次执行所有这些语句。

    我不得不通过这样做重写另一个程序员的一些程序,并且能够将处理时间缩短 90%。这是在第 6 版中,因此连接共享可能已经改进了这一点。

    【讨论】:

    • 这也是我的第一个想法。不幸的是,数据库连接不接受单个查询中的多个语句,我不相信这是我可以改变的。
    • 它不是多个查询合二为一,只是一个接一个地运行,就像你在SQL PLUS中运行它们都用分号隔开一样。我不太确定数据库如何限制这一点。
    • 您是否想在一个用分号分隔的 cfquery 块中放置多个插入语句?这可以并且受到用于 DB 连接的驱动程序的限制。我们正在使用内置的 Oracle JDBC 驱动程序,我不确定如何以这种方式进行配置。我知道在我的家庭服务器上使用 MySQL 时,驱动程序配置中有一个复选框来允许/禁止它;在我弄清楚之前让我头疼了好几天。 :P
    • 是的,这就是我要说的。并且默认设置的默认 Oracle 驱动程序将支持这一点。
    • 当我尝试这样做时,我收到一个 sql 语法错误。相同的语句单独工作,或在 PL/SQL 开发人员中一起工作。可能不是驱动程序导致此问题的原因;我刚刚根据我的 MySQL 经验做了一个假设。还有其他建议在哪里寻找这个?
    猜你喜欢
    • 2016-11-24
    • 2014-08-16
    • 1970-01-01
    • 1970-01-01
    • 2015-12-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-05-17
    相关资源
    最近更新 更多