我有一个mysql表,其中每个记录可以有无限的自定义字段(EAV模型,无关紧要),每个字段可以有无限的选项,每个选项可以有无限的值。
现在我正在尝试构建一个导出工具,它将使用它们的值导出所有这些自定义字段,即:name =>每个字段的值对。这不是重要的部分,它只是强调我们正在讨论单个记录的大量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);
}
基本上我统计所有记录并将其“分页”以便导出,然后遍历页面以避免一次加载太多的sql结果。
我想知道这是否是一种有效的方法?有什么想法吗?
我的替代方案是计算所有记录,将它们分成“页面”,并为每个页面执行ajax请求(在前一个请求成功完成后调用的递归函数)。在执行ajax请求时,可以同时处理1k条记录(这些1k也会像上例中那样被拆分,内部运行10次,例如100条结果),将它们写入临时目录(如part-1.csv, part-2.csv)并在处理完所有记录的最后,从包含所有csv部分的文件夹创建一个存档,并强制浏览器下载它然后从服务器中删除它(window.location.href最后ajax电话)。
这是上面的一个很好的替代品吗?
请注意,我的目标是限制内存使用量,这就是为什么我认为第二种方法可以帮助我更多。
请让我知道你的想法。
感谢。
答案 0 :(得分:4)
我的最后一个方法是第二个,在经过大量测试后我得出结论,在我的情况下,第二种方法在内存使用方面更好,即使完成整个导出的时间更长,也没有因为GUI将使用有关导出的实时统计信息进行更新,因此在等待导出完成时总体上是一种良好的用户体验。
这些是我采取的步骤:
1)加载页面并向服务器发出第一个ajax请求
2)服务器将一次批量读取100条记录中的前1000条记录,以避免从mysql中同时返回多条结果。
3)结果作为part-x.csv写入文件,其中x是ajax发送的请求号。
4)当没有更多记录要添加到文件中时,最后一个ajax调用将创建存档,并删除包含part-x.csv文件的文件夹。然后服务器将返回一个名为“download”的json param,它将包含通过PHP下载文件的url(fopen + fread + flush + fclose,然后取消链接存档文件)
5)使用“下载”参数,浏览器将执行window.location.href = json.download
并强制下载文件。
我知道,这样的工作更多,但正如我所说的,最终结果似乎比我第一次加载所有方式更好。
答案 1 :(得分:1)
以下是导出大型CSV文件的更优化方法(感谢@Joe以上代码) -
下面是示例JS函数 -
<script>
var exportedRecords = 0;
var chunkSize = 500; // as per query performance
for( start=0; start <= totalRecords; start += chunkSize){
chunkCSVExport(,0, chunkSize);
}
function chunkCSVExport(start,chunkSize){
requestData['start'] = start;
requestData['limit'] = chunkSize;
jQuery.ajax({
type : "post",
dataType : "json",
url : action,
data : formData,
success: function(response) {
console.log(response);
exportedRecords += chunkSize;
downloadfile();
}
});
}
function downloadfile(){
if(exportedRecords>=totalRecords){
// call download file function here
}
}
</script>
下面是示例PHP代码 -
<?php
$start = $_POST['start']; //added the missing closing single quote
$limit = $_POST['limit'];
if($start==0) {
$handle = fopen( 'file-export.csv', 'w' );
}else{
$handle = fopen( 'file-export.csv', 'a' );
}
// Run The query from start to limit
$results = getresults($query)
if($start==0) {
$headerDisplayed = false;
}else{
$headerDisplayed = true;
}
foreach ( $results as $data ) {
// Add a header row if it hasn't been added yet
if ( !$headerDisplayed ) {
// Use the keys from $data as the titles
fputcsv($handle, $arrHeaders);
$headerDisplayed = true;
}
// Put the data into the stream
fputcsv($handle, $data);
}
// Close the file
fclose($handle);
// Output some stuff for jquery to use
$response = array(
'result' => 'success'
);
echo json_encode($response);
exit;
?>