使用Laravel在MySQL中导入大型CSV文件

电机

我有一个csv文件,其范围可以从50k到超过10万行数据。

我目前正在使用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循环之类的方法遍历结果。

现在,这是我按时间顺序所面临的问题并修复/尝试:(1万行测试CSV文件)

  1. [问题] PHP超时。
  2. [解决]将其更改为通过作业命令异步运行。
  3. [结果]最多导入1500行。
  4. [问题]服务器内存不足。
  5. [补救措施]添加了1GB的交换驱动器。
  6. [结果]最多导入3000行。
  7. [问题]服务器内存不足。
  8. [补救措施]打开每个块250行的成块结果。
  9. [结果]最多导入5000行。
  10. [问题]服务器内存不足。
  11. [补救措施]删除了一些转置/联接表逻辑。
  12. [结果]最多导入7000行。

如您所见,结果微不足道,接近50k,我什至不能接近10k。

我已阅读并查看了可能的建议,例如:

  • 使用原始查询来运行“加载数据本地文件”。
  • 导入之前分割文件。
  • 将其存储在服务器上,然后将服务器拆分为文件,然后通过cron处理它们。
  • 不得已时,将我的512mb DO液滴升级到1gb。

加载本地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,而且这不是一个简单的上传,我需要处理并将其放入每行不同的相关表中,否则我会一次将所有数据加载到文件中。

谢谢!

丹尼尔·卡斯特罗

您似乎已经弄清楚了解释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出来与箱体的独白,你可以尝试,以使其适应您的需求。但是,如果最终还是使用了过多资源或无法满足您的需求,则可以采用更简单的方法。

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

然后,在脚本末尾,您可以将日志文件存储在任意位置。

禁用Laravel的查询日志

Laravel将所有查询存储在内存中,这可能对您的应用程序造成问题。幸运的是,您可以使用disableQueryLog方法释放一些宝贵的RAM。

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

必要时使用原始查询

我认为如果遵循这些技巧,您不太可能会再次耗尽内存,但是您总是可以牺牲Laravel的一些便利来提取最后的性能下降。

如果您了解使用SQL的方式,则可以对数据库执行原始查询


编辑:

至于超时问题,无论如何,您都应该按照注释中的建议将此代码作为排队的任务运行。插入这么多行将花费一些时间(特别是如果您有很多索引),并且用户不应长时间盯着无响应的页面。

本文收集自互联网,转载请注明来源。

如有侵权,请联系 [email protected] 删除。

编辑于
0

我来说两句

0 条评论
登录 后参与评论

相关文章