我有一个csv文件,范围从50k到超过100k行数据。
我目前正在使用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文件)
正如你所看到的结果是微不足道的并且远不及50k,我甚至几乎不能接近10k。
我已经阅读并研究了可能的建议,例如:
使用加载数据本地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 |
+----+------------+-------------+-------+---------+-------+--------+------+
因此,尽可能简单地快速浏览我的逻辑工作流程:
我希望有人可以与我分享一些关于如何解决这个问题的可能想法的一些见解,同时牢记使用Laravel,而且这不是一个简单的上传,我需要处理并将其放入不同的相关表中。我会立即加载数据。
谢谢!
答案 0 :(得分:7)
您似乎已经找到了解释CSV行并将其转换为在数据库上插入查询的逻辑,因此我将重点关注内存耗尽问题。
在PHP中处理大型文件时,任何将整个文件加载到内存的方法都会失败,变得难以忍受,或者需要比Droplet更多的RAM。
所以我的建议是:
使用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。
修改强>
至于超时问题, 应该将此代码作为排队任务运行,如同评论中所建议的那样。插入那么多行将花费一些时间(特别是如果你有很多索引)并且用户不应该长时间盯着无响应的页面。