Ruby-on-Rails 3.2:导出包含大型数据集(100,000 条记录)的 CSV

Posted

技术标签:

【中文标题】Ruby-on-Rails 3.2:导出包含大型数据集(100,000 条记录)的 CSV【英文标题】:Ruby-on-Rails 3.2: Export a CSV with a large data set (100,000 records) 【发布时间】:2012-05-02 00:03:54 【问题描述】:

简介

我的应用有多个表,有些有关联,有些没有关联。

有些表格需要容纳大约 100,000 个条目。

该应用在 Ruby 1.9 上使用 Rails 3.2,并托管在 Heroku 上。如果需要,我可以联系工作人员。

问题中的要求

应用程序的一个重要要求是允许用户将数据导出为 CSV - 对此的要求是允许用户过滤他们想要导出的数据,但我现在并不担心这一点,因为您从下面的数据中可以看出,我已经硬编码了要导出的数据,但这确实排除了创建一个 rake 任务来导出整个表。

此外,实现的方法必须考虑到允许被多个表使用,以避免不必要的代码重复。

当前解决方案

我正在我的应用程序中实现延迟作业并在作业中执行 CSV 生成。在执行此操作时,我正在关注来自“abdullah”的http://www.ayokasystems.com/blog/delegating-long-running-jobs-in-rails/ 的解决方案。

我们的想法是生成 CSV 格式的数据并将其保存在 UserJobs 表的 LONGTEXT 字段中,以便用户在完成后和以后下载。

问题

上述教程中使用的方法在我的应用程序中运行良好,直到我一次运行 100,000 条记录的作业。为了克服这个问题,我尝试将很酷的 find_each 函数添加到 perform 方法中,但是延迟的作业工作者每次尝试处理它时都会报告一个错误:

[Worker(host:*** pid:18637)] ReportJob failed with NoMethodError: undefined method `each' for #<Title:0x007ff20c1ec1b0> - 0 failed attempts
[Worker(host:*** pid:18637)] ReportJob failed with NoMethodError: undefined method `each' for #<Title:0x007ff20ec47f18> - 1 failed attempts
[Worker(host:*** pid:18637)] 2 jobs processed at 10.5219 j/s, 2 failed ... 

我的 perform 方法代码是:

def perform
  Title.find_each do |titles|
    csv_data = CSV.generate do |csv|
      titles.each do |t|
        csv << t.to_csv
      end
    end
    user_job = UserJob.find(user_job_id)
    user_job.update_attribute :data, csv_data
  end
end

任何人都可以看到问题可能是什么,我想我在循环事物的方式上犯了一个愚蠢的错误。

我非常愿意接受有关如何完成相关要求的任何其他建议,但请记住我对 Heroku 的限制。

【问题讨论】:

已经对我的代码进行了编辑,因为我刚刚看到我忘记将 CSV.generate 分配给 csv_data 以用于更新记录。 【参考方案1】:

您正在尝试使用 each 进行迭代,但在这种情况下,标题是标题的实例(不是数组)。

csv_vals = []
columns = [:name, :release_date, :studio]

Title.find_each(:select => columns) do |title| 
  columns.each |value| csv_vals << "#title[value]"
end

# comma separated string 
csv_string = csv_vals.join(',')

有更优雅的方式来制定 CSV 字符串,但我懒得尝试。

重要的是您只在需要的列上执行 SELECT。对于 100 000 条记录,这会大大减少带宽 DB 通信。只需 find_each,您就可以获得每一行的所有列,而您不需要它们。

【讨论】:

感谢您的回答,但另一个要求是此方法将来会接受来自其他表的数据,因此我不想实现选择。我知道在问题中我列出了会导致您做出假设的特定字段,但这仅供参考,我已修改问题以解决此问题。但是很好的答案,并且会记住这种技术以备不时之需。 您可以将列参数作为方法参数,代码将适用于不同的列。无论如何请记住,如果您要查找 find_each 并且有很多记录,请使用 select :) 对不起,我会,但我还没有足够的代表来做这件事。我会尽快做的! 我已经尝试了代码,但现在我在 ReportJob 的工作人员中遇到了一个不同的错误,原因是 'ActiveRecord::StatementInvalid: mysql2::Error: MySQL server has gone away' 这会破坏工作人员,这在对数据库进行更新时发生,在我放置在您的代码块下方的以下代码行中 - user_job.update_attribute :data, csv_string 您正在使用delayed_jobs 表并尝试写入列数据(那里没有数据列),除非您手动添加,默认情况下没有该列。【参考方案2】:

find_each 为块生成单个记录,而不是集合,因此您在单个记录上调用 each 时出错。看看find_in_batches,或者修复你的代码以使用单个记录:

Title.find_each do |title|
  CSV.generate do |csv|
    csv << title.to_csv
  end
  user_job = UserJob.find(user_job_id)
  user_job.update_attribute :data, csv_data
end

【讨论】:

顺便说一句,这只是为了解决您的特定错误 - 我不会对该代码的效率发表意见。 感谢您选择这个并引用 api!

以上是关于Ruby-on-Rails 3.2:导出包含大型数据集(100,000 条记录)的 CSV的主要内容,如果未能解决你的问题,请参考以下文章

如何对两个大型数据源 (csv) 进行比较

Ruby-on-Rails:如何摆脱“你被重定向”页面

ruby-on-rails - 嵌套资源问题

Ruby-on-Rails:多个 has_many :通过可能吗?

ubuntu中ruby-on-rails的安装

如何在 Ruby-on-Rails 中生成 PDF 表单