首先說明幾點(diǎn):
- excel格式的文件最大支持100萬的數(shù)據(jù),所以不考慮使用excel格式
- laravel的toArray()方法有內(nèi)存泄露,所以大量數(shù)據(jù)導(dǎo)出不能使用.
- 當(dāng)然要使用chunk方法查詢數(shù)據(jù),然后寫到文件中
關(guān)于toArray()方法內(nèi)存泄露排查
chunk()方法的代碼塊如下(chunk中按理是不用unset的,會自動釋放):
$ormQuery->chunk(1000, function ($data) use (&$firstWrite, $fp) {
Log::info("開始:".memory_get_usage());
$data = $data->toArray();
Log::info("結(jié)束:".memory_get_usage());
unset($data);
Log::info("usnet 結(jié)束:".memory_get_usage());
});
日志
從下往上看
通過日志我們可以分析得出:
在將一個“組塊”的 Eloquent 模型轉(zhuǎn)為數(shù)組的時(shí)候$data = $data->toArray();
內(nèi)存增加了很多.最后unset的時(shí)候又沒有釋放回初始值.實(shí)際上chunk中是不需要unset釋放的.
解決
使用
DB::table("xxx")->->orderBy(xx)->chunk(xx);
方式查詢數(shù)據(jù),查詢結(jié)果在使用$data = json_decode(json_encode($data), true);
轉(zhuǎn)換成數(shù)組,每次chunk執(zhí)行進(jìn)去,內(nèi)存都是一樣的,內(nèi)部也不需要unset.
大數(shù)據(jù)導(dǎo)出最后做的方案
因?yàn)閿?shù)據(jù)量大,所以我的做法是,小量數(shù)據(jù)導(dǎo)出就直接發(fā)送給用戶.大數(shù)據(jù)導(dǎo)出,走隊(duì)列任務(wù),然后做一個報(bào)表管理,用戶可以查看進(jìn)度和完成后下載.
在瀏覽器中直接返回文件和后臺寫入文件
函數(shù)基本使用
$fp = fopen("文件路徑/文件名", "a"); //a表示追加模式
fwrite($fp, chr(0xEF).chr(0xBB).chr(0xBF)); // 添加 BOM,不然excel打開csv是亂碼
在chunk的閉包中寫數(shù)據(jù): fputcsv($fp, $array);
fclose($fp);
在隊(duì)列中導(dǎo)出
$fp = fopen(storage_path('app/public/exports')."/".$report->name, "a");
fwrite($fp, chr(0xEF).chr(0xBB).chr(0xBF)); // 添加 BOM
$firstWrite = true;
$query = $expoter->customQuery($query);
$query->orderBy($tableName.".id")->chunk(1000, function ($data) use (&$firstWrite, $fp, $expoter) {
$data = json_decode(json_encode($data), true);
$data = $expoter->customData($data);
//有一些列總是不導(dǎo)出,如icon,image,images
$data = ExportUtils::removeInvalids($data);
//寫列名
if ($firstWrite) {
$columnNames = [];
//獲取列名
foreach ($data[0] as $key => $value) {
$columnNames[] = admin_translate($key, "coupon");
}
fputcsv($fp, $columnNames);
$firstWrite = false;
}
foreach ($data as $item) {
fputcsv($fp, $item);
}
});
fclose($fp);
在瀏覽器中返回?cái)?shù)據(jù)
$response = new StreamedResponse(null, 200, [
'Content-Type' => 'text/csv',
'Content-Disposition' => 'attachment; filename="'.$fileName.'"',
]);
$response->setCallback(function () use ($query, $tableName) {
$out = fopen('php://output', 'w');
fwrite($out, chr(0xEF).chr(0xBB).chr(0xBF)); // 添加 BOM
$firstWrite = true;
$query = $this->customQuery($query);
$query->orderBy($tableName.".id")->chunk(500, function ($data) use (&$firstWrite, $out) {
$data = json_decode(json_encode($data), true);
$data = $this->customData($data);
//有一些列總是不導(dǎo)出,如icon,image,images
$data = ExportUtils::removeInvalids($data);
//寫列名
if ($firstWrite) {
$columnNames = [];
//獲取列名
foreach ($data[0] as $key => $value) {
$columnNames[] = admin_translate($key, "coupon");
}
fputcsv($out, $columnNames);
$firstWrite = false;
}
foreach ($data as $item) {
fputcsv($out, $item);
}
});
fclose($out);
});
$response->send();