Ruby on rails 在rails中更快地导入CSV数据

Ruby on rails 在rails中更快地导入CSV数据,ruby-on-rails,Ruby On Rails,我正在构建一个导入模块,用于从csv文件导入大量订单。我有一个叫做Order的模型,数据需要存储在这个模型中 订单模型的简化版本如下所示 sku quantity value customer_email order_date status 导入数据时,必须发生两件事 任何日期或货币都需要清理,即日期在csv中表示为字符串,这需要转换为Rails日期对象,货币需要通过删除任何逗号或美元符号转换为十进制 如果行已存在,则必须更新该行,并基于两列检查唯一性 目前我使用一个简单的csv导入代码 CS

我正在构建一个导入模块,用于从csv文件导入大量订单。我有一个叫做Order的模型,数据需要存储在这个模型中

订单模型的简化版本如下所示

sku
quantity
value
customer_email
order_date
status
导入数据时,必须发生两件事

  • 任何日期或货币都需要清理,即日期在csv中表示为字符串,这需要转换为Rails日期对象,货币需要通过删除任何逗号或美元符号转换为十进制
  • 如果行已存在,则必须更新该行,并基于两列检查唯一性 目前我使用一个简单的csv导入代码

    CSV.foreach("orders.csv") do |row|
      order = Order.first_or_initialize(sku: row[0], customer_email: row[3])
      order.quantity = row[1]
      order.value= parse_currency(row[2])
      order.order_date = parse_date(row[4])
      order.status = row[5]
      order.save!
    end
    
    其中parse_currency和parse_date是用于从字符串中提取值的两个函数。对于date,它只是date.strTime的包装

    我可以添加一个检查来查看记录是否已经存在,如果它已经存在,则什么也不做,这样应该可以节省一点时间。但我正在寻找速度更快的东西。当前导入大约100k行需要30分钟,数据库为空。它将随着数据大小的增加而变慢

    所以我基本上是在寻找一种更快的方法来导入数据

    任何帮助都将不胜感激

    编辑

    在根据这里的评论进行了更多的测试之后,我有一个观察和一个问题。我不确定他们是否应该到这里来,或者我是否需要为这些问题打开一个新的思路。因此,如果我必须将此转移到另一个问题,请让我知道

    我使用Postgres copy运行了一个测试,从文件中导入数据,不到一分钟。我只是在没有任何验证的情况下将数据导入到一个新表中。因此,导入速度可以快得多

    头顶上的铁轨似乎来自两个地方

  • 正在发生的多个数据库调用,即每行的第一个或多个\u初始化。这最终会变成多个SQL调用,因为它必须首先找到记录,然后更新它,然后保存它
  • 带宽。每次调用SQL server时,数据都会来回流动,这会占用大量时间
  • 现在回答我的问题。如何将更新/创建逻辑移动到数据库,即,如果基于sku和客户电子邮件的订单已经存在,则需要更新记录,否则需要创建新记录。目前,对于rails,我正在使用first_或_initialize方法来获取记录,以防它存在并更新它,否则我将创建一个新记录并保存它。如何在SQL中实现这一点


    我可以使用ActiveRecord connection execute运行原始SQL查询,但我认为这不是一种非常优雅的方式。有更好的方法吗?

    因为ruby 1.9 fastcsv现在是ruby核心的一部分。你不需要使用特殊的宝石。只需使用
    CSV

    对于100k记录,ruby需要0.018秒/记录。在我看来,您的大部分时间将在
    顺序内使用。首先\u或\u初始化
    。这部分代码需要额外往返到数据库。初始化
    ActiveRecord
    也需要时间。但为了确保这一点,我建议您对代码进行基准测试

    Benchmark.bm do |x|
       x.report("CSV evel") { CSV.foreach("orders.csv") {} }
       x.report("Init: ") { 1.upto(100_000) {Order.first_or_initialize(sku:  rand(...), customer_email: rand(...))} } # use rand query to prevent query caching 
       x.report('parse_currency') { 1.upto(100_000) { parse_currency(...} }
       x.report('parse_date') { 1.upto(100_000) { parse_date(...} }
    end
    
    您还应该在导入过程中观察内存消耗情况。可能是垃圾收集没有足够频繁地运行,或者对象没有被清理

    要获得速度,您可以按照Matt Brictson提示并绕过
    ActiveRecord

    您可以尝试gem
    activerecord导入
    ,也可以开始并行,例如使用
    fork进行多处理
    或使用
    Thread进行多线程。new

    您好,欢迎使用Stack Overflow。过去有一个名为
    FasterCSV
    的gem-不确定它是否仍然是最新的。。。。也许值得研究一下。也就是说-谷歌搜索“rails csv gem fast”或其他东西,看看是否还有其他人…谢谢。我确实在搜索中遇到了更快的_csv,但它已经有几年没有更新了,所以我没有花太多时间在它上面。我查看了smarter_csv,它看起来是一个可能的选项,它允许您分块处理csv。但是我真的不知道如何在这些块上运行第一个\u或\u initialize,因为我必须在smarter\u csv返回的每个项目上运行它们。根据我的研究,我认为应该使用数据库事务,但我不确定如何在rails中做到这一点,并使其行为类似于first_或_initialize.hmmm,您应该能够在返回的任何行上使用
    first_或_initialize
    。。。块只是一行行,不是吗?考虑绕过AccVIECordRD。在纯Ruby中对CSV执行任何需要的处理,然后将CSV直接发送到数据库。MySQL和Postgres都有导入CSV的机制,而且速度非常快。是的,如果您没有任何回调和验证(Ruby对象实例化需要花费大量时间),请按照Matt所说的做,并使用数据库的唯一性约束作为检查。使用activerecord导入批量导入大型数据集+1。它将大大加快您的导入速度,因为它避免了每个记录的往返数据库。谢谢。根据您所说的,我正在进行一些测试,以找出问题所在,并意识到当我将其推送到heroku时,我遇到了额外的速度问题,因为在超过10K行左右后,它的内存不足,文件更大。所以我想知道是否有办法手动调用垃圾收集?您可以调用
    GC.start
    。此呼叫费用昂贵,因此性能可能会进一步下降。有时,循环引用可能导致内存泄漏。在这种情况下,Ruby无法识别不再需要对象。例如,有时它有助于将变量显式设置为nil,比如
    order=nil;循环结束时的行=nil