导出大型CSV文件

Twi*_*919 4 php csv

我有一个mysql表,其中每个记录可以具有无限的自定义字段(EAV模型,无关紧要),并且每个字段可以具有无限的选项,并且每个选项可以具有无限的值。
现在,我正在尝试构建一个导出工具,该工具将导出所有这些自定义字段及其值,即:每个字段的名称=>值对。这不是重要的部分,它只是在这里强调我们正在谈论很多针对单个记录的mysql查询,并且导出的大小将非常大。

对于主表中的每一行,我必须执行大约100个单独的sql查询,以获取字段,字段选项和字段选项值。这些查询非常快,因为它们都使用正确的索引,但是我们仍然在谈论单个记录的100个查询,我希望刚开始时我的主表中会有大约5万条记录。

现在,我要做的是:

set_time_limit(0);
ini_set('memory_limit', '1G');
ini_set("auto_detect_line_endings", true);

$count = $export->count();
$date = date('Y-m-d-H-i-s');
$fileName = CHtml::encode($export->name) .'-'. $date . '.csv';

$processAtOnce = 100;
$rounds = round($count / $processAtOnce);

header("Content-disposition: attachment; filename={$fileName}");
header("Content-Type: text/csv");

$headerSet = false;
for ($i = 0; $i < $rounds; ++$i) {

    $limit = $processAtOnce;
    $offset = $i * $processAtOnce;
    $rows = $export->find($limit, $offset);

    if (empty($rows)) {
        continue;
    }

    $outStream = fopen('php://output', 'w');

    if (!$headerSet) {
        fputcsv($outStream, array_keys($rows[0]), ',', '"');    
        $headerSet = true;
    }

    foreach ($rows as $row) {
        fputcsv($outStream, array_values($row), ',', '"');
    }

    echo fgets($outStream);

    fclose($outStream);
}
Run Code Online (Sandbox Code Playgroud)

基本上我会统计所有记录,然后将它们“分页”以便导出,然后遍历页面以避免一次加载太多sql结果。
我想知道这是否有效?有什么想法吗?

我的替代方法是对所有记录进行计数,将它们分成“页”,并为每个页执行一个ajax请求(在成功完成上一个请求之后调用的递归函数)。在执行ajax请求时,可能一次处理1k条记录(这些1k也将像上面的示例一样被拆分,在内部运行10次,例如获得100个结果),并将它们写入临时目录(如part-1.csv, part-2.csv),并在处理完所有记录后,从包含所有csv部分的文件夹中创建一个档案,并强制浏览器下载该档案,然后将其从服务器(window.location.href)中删除。最后一个ajax调用)。
这是上述的一个很好的选择吗?

请注意,我的目标是限制内存使用量,这就是为什么我认为第二种方法可以帮助我更多。

请让我知道你在想什么。
谢谢。

Twi*_*919 5

我的最后一种方法是第二种方法,经过大量测试,我得出结论:就我而言,第二种方法在内存使用方面要好得多,即使完成整个导出的时间更长,这也没有关系。 GUI将使用有关导出的实时统计信息进行更新,并且在等待导出完成时总体而言是良好的用户体验。

这些是我采取的步骤:
1)加载页面并向服务器发出第一个ajax请求。
2)服务器将一次读取前1000条记录,每批100条记录,以避免一次从mysql获得许多结果。
3)将结果作为part-x.csv写入文件,其中x是ajax发送的请求号。
4)当没有更多记录要添加到文件时,最后一个ajax调用将创建档案,并删除包含part-x.csv文件的文件夹。然后,服务器将返回一个名为“ download”的json参数,其中包含通过PHP下载文件的网址(fopen + fread + flush + fclose,然后取消链接存档文件)
5)使用“ download”参数,浏览器将执行window.location.href = json.download并强制下载文件。

我知道,这需要更多工作,但是正如我所说,最终结果似乎比以我第一次的方式一次加载所有内容更好。