簡體   English   中英

Rails批量插入多個表中

[英]Rails bulk insert in multiple tables

我有以下場景:我在Rails應用程序中導入了一些CSV,並且數據集的大小可能超過10萬行,這意味着使用了大量內存 - 我在服務器上沒有這些內存。

每個CSV代表一個表轉儲。
現在,我的問題是我需要在幾個表中導入數據,通過外鍵維護關系。

到目前為止我所做的大致是這樣的:

  • 創建id緩存哈希值
  • 對於每個CSV /表, find_or_initialize盡可能使用屬性,或執行類似model.where({complicated conditions}) || model.create({complicated conditions}) model.where({complicated conditions}) || model.create({complicated conditions})保存創建的對象
  • 填充ids緩存映射CSV id => DB id

complicated conditions語句中,可以放置保存在先前表中的一些ID並進行緩存。

有關詳細信息,請查看此處的代碼

注意:我需要的是更多的upsert而不僅僅是一個普通的insert

我已經嘗試過的一些優化:

  • 使用的事務=>使用的內存更少,插入更快
  • 二手crewait gem =>比普通AR快,但比交易慢
  • model.skip_callbacks(:create) =>加速或內存改進不明顯
  • 緩存了在所有其他表中廣泛使用的user模型=>高內存使用和慢速(?!)
  • 如果已存在行,則僅選擇id屬性以使用更少的內存=>速度/內存不會有很大差異
  • 緩存的優化哈希結構:使用Google哈希結構將ID存儲為INT-> INT =>內存使用量減少10%

我看過的其他東西卻無法弄清楚如何使用:

  • 單一和長SQL查詢:它基本上是crewait背后的想法,但就我嘗試而言它並不能很好地工作
  • activerecord-importactiverecord-import速度更快,但我會丟失所有關系或CSV到DB id映射
  • upsert :我已經看過了,但我想用它作為最后的手段(這有點棘手恕我直言)。

任何建議,關於如何改進的建議都是非常受歡迎的:談論工具,圖書館,戰略等等。

更新

這是我所擁有的CSV的簡化示例:

lings.csv

-----------------------------------
| id | name         | description |
-----------------------------------
| 0  | Subject_Verb | bla, bla... |
-----------------------------------
| 1  | Verb_Subject | bla, bla... |
-----------------------------------
| etc..                           |
-----------------------------------

properties.csv

--------------------------------------
| id | value | ling_id | property_id |
--------------------------------------
| 0  | Yes   |    0    |     0       |
--------------------------------------
| 1  | No    |    1    |     1       |
--------------------------------------
| etc..                              |
--------------------------------------

lings_properties.csv

 -------------------------------------- | id | value | ling_id | property_id | -------------------------------------- | 0 | Yes | 0 | 0 | -------------------------------------- | 1 | No | 1 | 1 | -------------------------------------- | etc.. | -------------------------------------- 

看看上面的例子,當我導入Lings和Properties時,將為它們分配不同的ID,但我仍然希望將LingsProperties鏈接到英語和法語。 我不能在數據庫中使用CSV ID - 它們由另一個應用程序分配,該應用程序具有與我導入它們的模式不同的模式。

更新2

我的Rails版本是3.0.20。 我正在使用Rails 3.2(或更高版本),我可以使用first_or_create (或類似的),但目前我已經陷入了Rails 3.0。

既然你提出了建議,我會給你一個,但沒有明示或暗示的保證。

我認為它可能更快,並且在同時構建ID映射的同時在一次傳遞中插入具有錯誤外鍵的所有記錄肯定會減少內存密集度(正如您所做的那樣)。 請注意,您可以使用帶有list參數的create將一批多條記錄發送到服務器。 這可能通過減少鎖定開銷而具有優勢。

然后使用update_all調用將好(新)外鍵替換為壞(舊)外鍵。 就像是:

PropertyOwnership.where(:ling_id => old_id).update_all('ling_id = ?', new_id) 

有了這個,你主要從處理循環中取出Active Record ORM,這應該有所幫助。 唯一的內存開銷應該是整數 - >整數id映射。

為了防止舊的ID與新的ID沖突,只需將從CSV讀取的外鍵字段增加一個大於表中當前最大id的數字加上其大小。 這應該使它超出插入期間創建的新id的范圍。

這應該更快的原因是update_all調用將完全在單個表中的服務器端發生,而find_or_initialize正在執行select,然后在保存時進行插入或更新,並且訪問在表中以深度優先順序發生。

最終,我設法保持相同的代碼結構,並找到適合我的特定場景的解決方案。

不幸的是,使用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)

上述解決方案的缺點:您必須為每個模型定制find_or_initialize_by方法,但是我已經設法刪除了GoogleHash結構,並且分析器顯示了在此過程中使用的內存的一半。 即使在時間方面,它也相當不錯,與之前的代碼庫相比,速度更快(~10%)。

在保存時禁用的驗證有助於減少內存使用量(約80%來自我的測試)以及find_or_initialize_by方法(~20%) - 我不知道(操作...) 可能需要多個參數

我打賭你可以在Rails 3.2中使用像find_or_initialize_by這樣的東西,更優雅:

model.class.where(complicated_conditions).first_or_create(complicated_conditions)

最后一個尚未測試,但是一旦我測試它將嘗試回來寫它。

注意 :在使用之前驗證CSV數據,基本上所有檢查都在此方面被禁用,因此在將文件傳遞給導入器之前對文件進行預處理!

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM