最近接到一個需求,通過選擇的時間段導出對應的訂單數據到excel中, 由於數據量較大,經常會有導出500K+數據的情況。平常的導出用的PHPexcle,將數據一次性讀取到內存中再一次性寫入文件,而在面對生成超大數據量的excel文件時這顯然是會造成內存溢出的。
這時就需要循環批量寫入excle導出。
過程中可能遇到的問題:
1.超時:Maximum execution time of 30 seconds exceeded
解決:在文件開頭加上 set_time_limit(0);即php執行時間不受限制。
2.內存溢出:Allowed memory size of 134217728 bytes exhausted (tried to allocate 20480 bytes)
解決:
2.1 可在ini文件設置;設置memory_limit,將內存設置加大,
2.2 在執行代碼前加上 ini_set("memory_limit", "1024M");
2.3 每次讀取的數據量減少,增加循環的次數;
3.導出的excle數字過長展示成科學計數
解決:
3.1 在要寫入的數字數據,後面加個 “\t”;
3.2 或是數字轉換為字符串格式
上代碼(生成excle鏈接導出數據):
public function downBigExcle()
{
set_time_limit(0);
#自定義導出地址+文件名
$pathName = time().'.csv';
#計算要導出總數據條數
$totalNums = Db::table('t_excle')->count('id');
#設置表頭標題
$headerTitleArr = ['xx','xxx'];
#CSV的Excel支持GBK編碼,一定要轉換,否則亂碼
foreach ($headerTitleArr as $i => $v) {
$headerTitleArr[$i] = iconv('utf-8', 'gbk', $v);
}
#每次查詢的條數
$pageSize = 5000;
#分批導的次數
$pages = ceil($totalNums / $pageSize);
#打開文件流
$fp = fopen($pathName, 'a');
#將數據格式化為CSV格式並寫入到文件流中
fputcsv($fp, $headerTitleArr);
#主體內容
$paegNum = 0;
for ($i=0; $i
$data = Db::table('t_excle')->limit($paegNum,$pageSize)->field('*')->select();
foreach ($data as $fields) {
foreach ($fields as $key => &$v) {
// CSV的Excel支持GBK編碼,一定要轉換,否則亂碼
// 加"\t"防止數字導出時變成科學計數
$v = iconv('utf-8', 'gbk', $v."\t");
}
fputcsv($fp, $fields);
//這邊可記錄下數據的最後一次的位置,便於查看排錯定位。
}
unset($data);//釋放變量的內存
$paegNum += $pageSize;
}
fclose($fp);
#輸出下載鏈接,url為下載的域名
return $url.$pathName;
}
對於一個請求來說PHP是單線程的,大數據量的導出又是最耗時的,搞不好其他請求就阻塞了。
小數據量的話,可以直接瀏覽器輸出;
大數據量的話,可以生成excle下載鏈接;推薦使用swoole異步框架處理請求。
1.當客服在後臺系統點擊下載後,可先返回個提示或是生成一條下載記錄;
2.在後臺異步處理excle導出操作;
3.客服主動刷新頁面查看是否有下載鏈接生成。
再上代碼(生成excle直接瀏覽器輸出):
public function downOrderData($timeStart, $timeEnd)
{
set_time_limit(0);
$columns = [
'序號ID', '姓名', '電話', ......
];
$csvFileName = '訂單數據' . $timeStart .'_'. $timeEnd . '.xlsx';
//設置好告訴瀏覽器要下載excel文件的headers
header('Content-Description: File Transfer');
header('Content-Type: application/vnd.ms-excel');
header('Content-Disposition: attachment; filename="'. $fileName .'"');
header('Expires: 0');
header('Cache-Control: must-revalidate');
header('Pragma: public');
$fp = fopen('php://output', 'a');//打開output流
mb_convert_variables('GBK', 'UTF-8', $columns);
fputcsv($fp, $columns);//將數據格式化為CSV格式並寫入到output流中
$accessNum = '100000'//從數據庫獲取總量,假設是十萬
$perSize = 1000;//每次查詢的條數
$pages = ceil($accessNum / $perSize);
$lastId = 0;
for($i = 1; $i <= $pages; $i++) {
$data = Db::table('t_excle')->limit($lastId ,$pageSize)->field('*')->select();
foreach($data as $value) {
mb_convert_variables('GBK', 'UTF-8', $value);
fputcsv($fp, $value);
$lastId = $value['id'];
}
unset($data);//釋放變量的內存
//刷新輸出緩衝到瀏覽器
ob_flush();
flush();//必須同時使用 ob_flush() 和flush() 函數來刷新輸出緩衝。
}
fclose($fp);
exit();
}
PS:更多信息交流,請關照:倒影Amoy
活著就是為了改變世界,難道還有其他原因嗎?----喬布斯
請輸入圖片描述