2015-12-29 81 views
2

我有一個csv文件,其範圍可以從50k到超過100k行數據。使用Laravel在MySQL中導入大型CSV文件

我目前使用Laravel w/Laravel Forge,MySQL和Maatwebsite Laravel Excel軟件包。

這是由最終用戶使用,而不是自己,所以我創建了一個簡單的表單上我的刀片鑑於這樣:

{!! Form::open(
    array(
     'route' => 'import.store', 
     'class' => 'form', 
     'id' => 'upload', 
     'novalidate' => 'novalidate', 
     'files' => true)) !!} 

    <div class="form-group"> 
     <h3>CSV Product Import</h3> 
     {!! Form::file('upload_file', null, array('class' => 'file')) !!} 
    </div> 

    <div class="form-group"> 
     {!! Form::submit('Upload Products', array('class' => 'btn btn-success')) !!} 
    </div> 
{!! Form::close() !!} 

這則存儲在服務器上的文件,成功地和我現在可以使用諸如foreach循環之類的東西遍歷結果。

現在,這裏是我面臨的時間順序和修復/企圖的問題: (10K行測試CSV文件)

  1. [問題] PHP超時。
  2. [remedy]將其更改爲通過作業命令異步運行。
  3. [結果]進口多達1500行。
  4. [問題]服務器內存不足。
  5. [補救]增加了1GB的交換驅動器。
  6. [結果]最多可導入3000行。
  7. [問題]服務器內存不足。
  8. [補救]打開每個塊的250行分塊結果。
  9. [結果]最多可導入5000行。
  10. [問題]服務器內存不足。
  11. [修正]刪除了一些轉置/連接表邏輯。
  12. [結果]進口多達7000行。

正如你所看到的結果是邊際和遠不及50k,我幾乎可以使它接近10k。

我讀過了,看着可行的建議,如:

  • 使用原始查詢運行LOAD DATA LOCAL INFILE。
  • 導入前分割文件。
  • 在服務器上存儲,然後將服務器分割成文件並使用cron處理它們。
  • 作爲最後的手段將我的512mb DO溶滴升級到1GB。

與LOAD DATA LOCAL INFILE走向可能無法工作,因爲我的標題列可能每個文件,這就是爲什麼我有邏輯處理/遍歷它們改變。

在導入之前拆分文件在10k以下是不錯的,但是對於50k以上的版本嗎?這將是非常不切實際的。

存儲在服務器上,然後讓服務器拆分它並單獨運行它們,而不會讓最終用戶困擾?可能但不確定如何在PHP中實現這一點,但只是簡要閱讀一下。

另外要注意,我的隊列工作設置10000秒,這也是非常不切實際和壞實踐超時,但似乎這是它會繼續運行內存佔用一擊之前的唯一途徑。

現在我可以給,並剛剛升級顯存爲1GB,但我覺得充其量再次失敗之前它可以跳到我20K行。有些東西需要快速高效地處理所有這些行。

最後,這裏是我的表結構的一瞥:

Inventory 
+----+------------+-------------+-------+---------+ 
| id | profile_id | category_id | sku | title | 
+----+------------+-------------+-------+---------+ 
| 1 |   50 |  51234 | mysku | mytitle | 
+----+------------+-------------+-------+---------+ 

Profile 
+----+---------------+ 
| id |  name  | 
+----+---------------+ 
| 50 | myprofilename | 
+----+---------------+ 

Category 
+----+------------+--------+ 
| id | categoryId | name | 
+----+------------+--------+ 
| 1 |  51234 | brakes | 
+----+------------+--------+ 

Specifics 
+----+---------------------+------------+-------+ 
| id | specificsCategoryId | categoryId | name | 
+----+---------------------+------------+-------+ 
| 1 |     20 |  57357 | make | 
| 2 |     20 |  57357 | model | 
| 3 |     20 |  57357 | year | 
+----+---------------------+------------+-------+ 

SpecificsValues 
+----+-------------+-------+--------+ 
| id | inventoryId | name | value | 
+----+-------------+-------+--------+ 
| 1 |   1 | make | honda | 
| 2 |   1 | model | accord | 
| 3 |   1 | year | 1998 | 
+----+-------------+-------+--------+ 

Full CSV Sample 
+----+------------+-------------+-------+---------+-------+--------+------+ 
| id | profile_id | category_id | sku | title | make | model | year | 
+----+------------+-------------+-------+---------+-------+--------+------+ 
| 1 |   50 |  51234 | mysku | mytitle | honda | accord | 1998 | 
+----+------------+-------------+-------+---------+-------+--------+------+ 

所以我的邏輯流程儘可能簡單的快速運行,通過將是:

  1. 加載文件到Maatwebsite/Laravel -Excel並通過分塊循環
  2. 檢查迭代如果CATEGORY_ID和SKU是空否則忽略並記錄錯誤到一個數組。
  3. 查找category_id並從它使用的所有相關表中拉出所有相關的列字段,然後如果沒有null插入數據庫。
  4. 使用文件中可用字段的更多邏輯來生成自定義標題。
  5. 沖洗並重復。
  6. 最後將錯誤數組導出到文件中,並將其記錄到數據庫中以供下載,以便在最後查看錯誤。

我希望有人能和我一起上,我應該如何解決這個同時牢記使用Laravel的一些可能的想法分享一些見解,也認爲它不是一個簡單的上傳我需要處理並投入不同的相關表每行其他我會加載數據infile它一次。

謝謝!

+0

所有的csv文件都被插入到同一個表中嗎?如果是這種情況,我不明白爲什麼使用'load data local infile'會是一個問題 - 有些列只是'NULL'。您可以使用Python(通過'exec()')通過PHP子進程執行,以便在上載到服務器之後但在將其插入表之前根據需要解析文件。 – Terry

+0

@Terry它只是一個CSV文件,但如上所述插入到多個表中,爲什麼我無法輕鬆使用本地infile的加載數據。此外,每個文件的數據更改取決於涉及哪些categoryid,這些列將具有不同的列。也因爲這個變量,現在很難指定每個字段的數據類型。 – dmotors

+0

如果它只是一個CSV文件,然後使用Maatwebsite Laravel Excel包和PHPExcel是矯枉過正,雖然Maatwebsite Laravel Excel包(我相信)提供訪問PHPExcel chunking函數來加載文件 –

回答

4

你似乎已經想通了邏輯解釋的CSV線,將它們轉換爲數據庫中插入查詢,所以我將專注於內存耗盡的問題。

當與PHP大型文件,整個文件加載到內存要麼失敗的任何方法,成爲不能忍受緩慢或需要更多的內存比你滴了。

所以我的建議是:

使用fgetcsv

$handle = fopen('file.csv', 'r'); 
if ($handle) { 
    while ($line = fgetcsv($handle)) { 
     // Process this line and save to database 
    } 
} 

這樣只有一行在將被加載到內存中的時間逐行讀取文件中的行。然後,您可以處理它,保存到數據庫,並用下一個覆蓋它。

保持一個單獨的文件句柄記錄

你的服務器是短暫的記憶,所以錯誤記錄到一個數組可能不是一個好主意,因爲所有的錯誤都將被保存在它。如果您的csv有大量空skus和類別id的條目,那麼這可能會成爲問題。

Laravel出來與Monolog箱子,你可以嘗試,以使其適應您的需求。但是,如果它最終還是使用了太多的資源,或者不適合您的需求,那麼更簡單的方法可能就是解決方案。

$log = fopen('log.txt', 'w'); 
if (some_condition) { 
    fwrite($log, $text . PHP_EOL); 
} 

然後,在腳本的末尾,您可以將日誌文件存儲到任何地方。

禁用Laravel的查詢日誌

Laravel保持存儲在內存中您所有的疑問,而這可能是您的應用程序有問題。幸運的是,您可以使用disableQueryLog method來釋放一些珍貴的RAM。

DB::connection()->disableQueryLog(); 

使用原始查詢,如果需要的話

我認爲這是不可能的,你將再次耗盡內存,如果你遵循這些提示,但你總是可以犧牲一些Laravel的便利,以提取最後一滴血的表現。

如果你知道你的SQL的方式,你可以execute raw queries to the database


編輯:

至於超時問題,您應該運行該代碼爲排隊的任務,因爲在意見建議不管。插入那麼多行需要一些時間(特別是如果你有很多索引),並且用戶不應該長時間盯着沒有響應的頁面。

+0

偉大的建議。我禁用了查詢日誌,並將我的Maatwebsite Laravel Excel轉換爲使用您建議的fgetcsv示例。它目前正在運行,我的記憶一直沒有飆升。我確實有一個問題,一次只能用1塊(使用laravel excel軟件包)與fgetcsv 1一樣分塊,還是會一直妨礙並耗盡內存? – dmotors

+0

我不知道Laravel-Excel如何特別分塊,所以我不能回答這個問題。但是,您可以非常輕鬆地修改fgetcsv循環,以便一次讀取更多行,從而在不使用太多內存的情況下提高性能。 –

+0

它達到30k行,這是一個巨大的差異相比,7k。我的隊列工作人員有一個10000秒的超時時間,所以我會把它提高到一個很高的數字,因爲Laravel Forge似乎並沒有讓我沒有超時。我會認爲這是一個可行的解決方案。 – dmotors