Rails批量插入多个表中

Mar*_*coL 5 ruby mysql sql csv ruby-on-rails

我有以下场景:我在Rails应用程序中导入了一些CSV,并且数据集的大小可能超过10万行,这意味着使用了大量内存 - 我在服务器上没有这些内存.

每个CSV代表一个表转储.
现在,我的问题是我需要在几个表中导入数据,通过外键维护关系.

到目前为止我所做的大致是这样的:

  • 创建id缓存哈希值
  • 对于每个CSV /表find_or_initialize,尽可能使用属性,或执行model.where({complicated conditions}) || model.create({complicated conditions})保存创建的对象之类的操作
  • 填充ids缓存mapping CSV id=>DB id

complicated conditions语句中可以放置保存在以前的表中并缓存的一些ID.

看看在这里的代码,了解更多详情.

注意:我需要的upsert不仅仅是一个平原insert.

我已经尝试过的一些优化:

  • 使用的事务=>使用的内存更少,插入更快
  • 使用crewaitgem =>比普通AR快,但比事务慢
  • model.skip_callbacks(:create) =>加速或内存改进不明显
  • 缓存user在所有其他表中广泛使用的模型=>高内存使用和慢速(?!)
  • 如果已存在行,则仅选择id要使用较少内存的属性=>速度/内存不会有很大差异
  • 缓存的优化哈希结构:使用Google哈希结构将ID存储为INT-> INT =>内存使用量减少10%

我看过的其他东西却无法弄清楚如何使用:

  • 单个和长SQL查询:它基本上是背后的想法,crewait但就我尝试而言它并不能很好地工作
  • activerecord-import:导入速度更快,但我会失去所有关系或CSV到数据库ID映射
  • upsert:我已经看过了,但我想用它作为最后的手段(这有点棘手恕我直言).

任何建议,关于如何改进的建议都是非常受欢迎的:谈论工具,图书馆,战略等等.

更新

这是我所拥有的CSV的简化示例:

lings.csv

------------------------
| id | name    | depth |
------------------------
| 0  | English |   0   |
------------------------
| 1  | French  |   0   |
------------------------
| etc..                |
------------------------
Run Code Online (Sandbox Code Playgroud)

properties.csv

-----------------------------------
| id | name         | description |
-----------------------------------
| 0  | Subject_Verb | bla, bla... |
-----------------------------------
| 1  | Verb_Subject | bla, bla... |
-----------------------------------
| etc..                           |
-----------------------------------
Run Code Online (Sandbox Code Playgroud)

lings_properties.csv

--------------------------------------
| id | value | ling_id | property_id |
--------------------------------------
| 0  | Yes   |    0    |     0       |
--------------------------------------
| 1  | No    |    1    |     1       |
--------------------------------------
| etc..                              |
--------------------------------------
Run Code Online (Sandbox Code Playgroud)

看看上面的例子,当我导入Lings和Properties时,将为它们分配不同的ID,但我仍然希望将LingsProperties链接到英语和法语.我不能在数据库中使用CSV ID - 它们由另一个应用程序分配,该应用程序具有与我导入它们的模式不同的模式.

更新2

我的Rails版本是3.0.20.我正在使用Rails 3.2(或更高版本),我可以使用first_or_create(或类似),但目前我仍然坚持使用Rails 3.0.

Mar*_*coL 0

最终我设法保持相同的代码结构并找到适合我的特定场景的解决方案。

不幸的是,对于 Rails 3.0,我没有那么多选择,所以我只是想出了以下模式:

model.class.skip_callback(:create)
model.class.transaction do
  CSV.foreach(file_path, :headers => true) do |row|
    // unfortunately this bit here has to be customised on the model
    // so append after the _by_ all the conditions you are looking for
    item = model.class.find_or_initialize_by_this_and_that(this, that, ...) do |m|
      m.more = row["more"]
    end


    item.save!(:validate => false)

    ids_cache[row["id"]] = item.id

  end
end
model.class.set_callback(:create)
Run Code Online (Sandbox Code Playgroud)

上述解决方案的缺点:您必须find_or_initialize_by为每个模型定制方法,但我已经成功删除了 GoogleHash 结构,并且分析器显示在此过程中使用的内存减少了一半。即使从时间角度来看,它也相当不错,与之前的代码库相比,加速幅度较小(约 10%)。

保存时禁用的验证对降低内存使用量(我的测试中的〜80%)以及方法find_or_initialize_by(〜20%)有很大帮助 - 我不知道(操作...)可以接受多个参数

我打赌你find_or_initialize_by也可以在 Rails 3.2 中使用类似的东西,甚至更优雅:

model.class.where(complicated_conditions).first_or_create(complicated_conditions)
Run Code Online (Sandbox Code Playgroud)

最后一个还没有测试过,但是一旦我测试了它就会尝试回来写下来。

注意:在使用此数据之前验证 CSV 数据,基本上所有检查都在这一侧被禁用,因此在将文件传递给导入器之前对文件进行预处理!