使用 fgetcsv
,我是否可以以某种方式进行破坏性读取,其中我读取和处理的行将被丢弃,因此如果我在第一次传递中没有完成整个文件,我可以回来并在the script timed out
之前从我离开的地方继续
其他详细信息:
我从供应商那里收到每日产品提要,该文件为 200mb .gz文件。 当我解压缩文件时,它变成了一个 1.5gb 的.csv,有近 500,000 行和 20 - 25 个字段。 我需要将此信息读取到 MySQL 数据库中,最好是使用 PHP,以便我可以安排 CRON 每天在我的网络托管服务提供商处运行脚本。
托管服务提供商将服务器上的硬超时设置为 180 秒,任何单个脚本的最大内存利用率限制为 128mb。 这些限制是我无法改变的。
我的想法是使用 fgetcsv 函数从.csv中获取信息,但由于 3 分钟超时,我预计必须在文件中进行多次传递,我认为在处理文件时缩减文件会很好,这样我就不需要花费周期跳过在上一次传递中已经处理过的行。
从您的问题描述来看,听起来确实需要切换主机。处理具有硬时间限制的 2 GB 文件不是一个非常有建设性的环境。话虽如此,从文件中删除读取行的建设性甚至更低,因为您必须将整个 2 GB 重写到磁盘减去您已经读取的部分,这非常昂贵。
假设您保存了已处理的行数,则可以跳过如下行:
$alreadyProcessed = 42; // for example
$i = 0;
while ($row = fgetcsv($fileHandle)) {
if ($i++ < $alreadyProcessed) {
continue;
}
...
}
但是,这意味着您每次浏览整个 2 GB 文件时都会从头开始读取它,这本身已经需要一段时间,并且每次重新开始时,您都可以处理越来越少的行。
这里最好的解决方案是记住文件指针的当前位置,ftell
是您要查找的函数:
$lastPosition = file_get_contents('last_position.txt');
$fh = fopen('my.csv', 'r');
fseek($fh, $lastPosition);
while ($row = fgetcsv($fh)) {
...
file_put_contents('last_position.txt', ftell($fh));
}
这使您可以直接跳回到上次的位置并继续阅读。您显然希望在此处添加大量错误处理,因此无论您的脚本在哪个点中断,您都不会处于不一致的状态。
在像 Stream 一样读取时,可以在一定程度上避免超时和内存错误。通过逐行读取,然后将每一行插入数据库(或相应地处理)。这样,每次迭代时内存中仅保留一行。请注意,不要尝试将巨大的csv文件加载到数组中,这确实会消耗大量内存。
if(($handle = fopen("yourHugeCSV.csv", 'r')) !== false)
{
// Get the first row (Header)
$header = fgetcsv($handle);
// loop through the file line-by-line
while(($data = fgetcsv($handle)) !== false)
{
// Process Your Data
unset($data);
}
fclose($handle);
}
更好的解决方案(连续倒带并写入打开的文件流在效率上是非常低效的)是跟踪读取的每条记录的文件位置(使用 ftell)并将其与您读取的数据一起存储 - 然后,如果您必须恢复,那么只需搜索到最后一个位置。
您可以尝试使用 mysql 的读取文件功能直接加载文件(这可能会快得多),尽管我过去遇到过这个问题并最终编写了自己的 php 代码。
托管服务提供商将服务器上的硬超时设置为 180 秒,任何单个脚本的最大内存利用率限制为 128mb。这些限制是我无法改变的。
你试过什么?
内存可以通过 php.ini 文件以外的其他方式限制,但我无法想象有人如何真正阻止您使用不同的执行时间(即使禁用ini_set,您也可以从命令行运行 php -d max_execution_time=3000/your/script.php 或 php -c/path/to/custom/inifile/your/script.php
除非您尝试将整个数据文件放入内存中,否则内存限制为 128Mb 应该没有问题