概述:
最近公司項目要求把數(shù)據(jù)除了頁面輸出也希望有導出功能永乌,雖然之前也做過幾個導出功能吨述,但這次數(shù)據(jù)量相對比較大,差不多一天數(shù)據(jù)就20W條歧寺,要求導7天或者30天燥狰,那么數(shù)據(jù)量就輕松破百萬了甚至破千萬,因此開發(fā)的過程中發(fā)現(xiàn)了一些大數(shù)據(jù)導出的坑斜筐,在此跟大家分享一下龙致,互相學習。
準備:
1顷链、PHP設置坑:
set_time_limit – 設置腳本最大執(zhí)行時間:
此配置一般PHP默認是30秒净当,如果你是數(shù)據(jù)小的,可能就不會發(fā)現(xiàn)有該設置問題蕴潦,但如果你數(shù)據(jù)達到了百萬級導出,往往30秒是不夠的俘闯,因此你需要在你的腳本中添加 set_time_limit(0)潭苞,讓該腳本沒有執(zhí)行時間現(xiàn)在
memory_limit – PHP的內(nèi)存限定:
此配置一般php默認是128M,如果之前做過小數(shù)據(jù)的朋友可能也會動過這個配置就能解決許多問題,或許有人想真朗,你大數(shù)據(jù)也把這個調(diào)大不就行了嗎此疹?那么真的是too young too native了,你本地能設置1G或者無限制或許真的沒問題,但是正式場蝗碎,你這么搞遲早會出事的湖笨,一個PHP程序占那么大的內(nèi)存的空間,如果你叫你公司運維幫忙調(diào)一下配置蹦骑,估計運維一定很不情愿慈省,服務器硬件這么搞也是太奢侈了。所以說眠菇,我們要盡量避免調(diào)大該設置边败。
2、excel坑:
既然是導出數(shù)據(jù)捎废,大伙們當然馬上想到了excel格式了笑窜,多方便查看數(shù)據(jù)呀,然而萬萬沒想到excel也是有脾氣的呀登疗!
表數(shù)據(jù)限制:
1排截、Excel 2003及以下的版本。一張表最大支持65536行數(shù)據(jù)辐益,256列断傲。
2、Excel 2007-2010版本荷腊。一張表最大支持1048576行艳悔,16384列。
也就是說你想幾百萬條輕輕松松一次性導入一張EXCEL表是不行的女仰,你起碼需要進行數(shù)據(jù)分割猜年,保證數(shù)據(jù)不能超過104W一張表。
PHPexcel內(nèi)存溢出:
既然數(shù)據(jù)限制在104W疾忍,那么數(shù)據(jù)分割就數(shù)據(jù)分割唄乔外,于是你嘗試50W一次導入表,然而PHPexcel內(nèi)部有函數(shù)報內(nèi)存溢出錯誤一罩,然后你就不斷的調(diào)小數(shù)據(jù)量杨幼,直到5W一次導入你都會發(fā)現(xiàn)有內(nèi)存溢出錯誤。這是為什么呢聂渊,雖然你分割數(shù)據(jù)來導入多個數(shù)據(jù)表差购,但是最后PHPexcel內(nèi)部還是一次性把所有表數(shù)據(jù)放進一個變量中來創(chuàng)建文件……額,這幾百萬數(shù)據(jù)一個變量存儲汉嗽,你想內(nèi)存不溢出欲逃,還真有點困難。
(后來看了一些文章發(fā)現(xiàn)PHPExcel也有解決方案饼暑,PHPExcel_Settings::setCacheStorageMethod方法更改緩沖方式來減小內(nèi)存的使用)
3稳析、csv坑:
EXCEL這么麻煩洗做,我不用還不行嗎?我用csv文件儲存彰居,既不限制數(shù)量诚纸,還能直接用EXCEL來查看,又能以后把文件導入數(shù)據(jù)庫陈惰,一舉幾得豈不是美哉畦徘?咦,少俠好想法奴潘!但是CSV也有坑哦旧烧!
輸出buffer過多:
當你用PHP原生函數(shù)putcsv()其實就使用到了輸出緩存buffer,如果你把幾百萬的數(shù)據(jù)一直用這個函數(shù)輸出画髓,會導致輸出緩存太大而報錯的掘剪,因此我們每隔一定量的時候,必須進行將輸出緩存中的內(nèi)容取出來奈虾,設置為等待輸出狀態(tài)夺谁。具體操作是:
1、ob_flush();
2肉微、flush();
具體說明介紹:PHP flush()與ob_flush()的區(qū)別詳解
EXCEL查看CSV文件數(shù)量限制:
大多數(shù)人看csv文件都是直接用EXCEL打開的匾鸥。額,這不就是回到EXCEL坑中了嗎碉纳?EXCEL有數(shù)據(jù)顯示限制呀勿负,你幾百萬數(shù)據(jù)只給你看104W而已。什么劳曹?你不管奴愉?那是他們打開方式不對而已?不好不好铁孵,我們解決也不難呀锭硼,我們也把數(shù)據(jù)分割一下就好了,再分開csv文件保存蜕劝,反正你不分割數(shù)據(jù)變量也會內(nèi)存溢出檀头。
4、總結(jié)做法
分析完上面那些坑岖沛,那么我們的解決方案來了暑始,假設數(shù)據(jù)量是幾百萬。
1婴削、那么我們要從數(shù)據(jù)庫中讀取要進行數(shù)據(jù)量分批讀取蒋荚,以防變量內(nèi)存溢出,
2馆蠕、我們選擇數(shù)據(jù)保存文件格式是csv文件期升,以方便導出之后的閱讀、導入數(shù)據(jù)庫等操作互躬。
3播赁、以防不方便excel讀取csv文件,我們需要104W之前就得把數(shù)據(jù)分割進行多個csv文件保存
4吼渡、多個csv文件輸出給用戶下載是不友好的容为,我們還需要把多個csv文件進行壓縮,最后提供給一個ZIP格式的壓縮包給用戶下載就好寺酪。
代碼:
//導出說明:因為EXCEL單表只能顯示104W數(shù)據(jù)坎背,同時使用PHPEXCEL容易因為數(shù)據(jù)量太大而導致占用內(nèi)存過大,
//因此寄雀,數(shù)據(jù)的輸出用csv文件的格式輸出得滤,但是csv文件用EXCEL軟件讀取同樣會存在只能顯示104W的情況,所以將數(shù)據(jù)分割保存在多個csv文件中盒犹,并且最后壓縮成zip文件提供下載
function putCsv(array $head, $data, $mark = 'attack_ip_info', $fileName = "test.csv")
{
set_time*limit(0);
$sqlCount = $data->count();
// 輸出Excel文件頭懂更,可把user.csv換成你要的文件名
header('Content-Type: application/vnd.ms-excel;charset=utf-8');
header('Content-Disposition: attachment;filename="' . $fileName . '"');
header('Cache-Control: max-age=0');
$sqlLimit = 100000;//每次只從數(shù)據(jù)庫取100000條以防變量緩存太大
// 每隔$limit行,刷新一下輸出buffer急膀,不要太大沮协,也不要太小
$limit = 100000;
// buffer計數(shù)器
$cnt = 0;
$fileNameArr = array();
// 逐行取出數(shù)據(jù),不浪費內(nèi)存
for ($i = 0; $i < ceil($sqlCount / $sqlLimit); $i++) {
$fp = fopen($mark . '*' . $i . '.csv', 'w'); //生成臨時文件
// chmod('attack_ip*info*' . $i . '.csv',777);//修改可執(zhí)行權(quán)限
$fileNameArr[] = $mark . '_' . $i . '.csv';
// 將數(shù)據(jù)通過fputcsv寫到文件句柄
fputcsv($fp, $head);
$dataArr = $data->offset($i * $sqlLimit)->limit($sqlLimit)->get()->toArray();
foreach ($dataArr as $a) {
$cnt++;
if ($limit == $cnt) {
//刷新一下輸出buffer卓嫂,防止由于數(shù)據(jù)過多造成問題
ob_flush();
flush();
$cnt = 0;
}
fputcsv($fp, $a);
}
fclose($fp); //每生成一個文件關(guān)閉
}
//進行多個文件壓縮
$zip = new ZipArchive();
$filename = $mark . ".zip";
$zip->open($filename, ZipArchive::CREATE); //打開壓縮包
foreach ($fileNameArr as $file) {
$zip->addFile($file, basename($file)); //向壓縮包中添加文件
}
$zip->close(); //關(guān)閉壓縮包
foreach ($fileNameArr as $file) {
unlink($file); //刪除csv臨時文件
}
//輸出壓縮文件提供下載
header("Cache-Control: max-age=0");
header("Content-Description: File Transfer");
header('Content-disposition: attachment; filename=' . basename($filename)); // 文件名
header("Content-Type: application/zip"); // zip格式的
header("Content-Transfer-Encoding: binary"); //
header('Content-Length: ' . filesize($filename)); //
@readfile($filename);//輸出文件;
unlink($filename); //刪除壓縮包臨時文件
}
總結(jié):
其實上面代碼還是有優(yōu)化的空間的慷暂,比如說用異常捕捉,以防因為某些錯誤而導致生成了一些臨時文件又沒有正常刪除晨雳,還有PHPexcel的緩存設置也許能解決內(nèi)存溢出問題行瑞,可以生成一個EXCEL文件多個工作表的形式,這樣對于文件閱讀者來說更友好悍募。
原文來源:https://blog.csdn.net/tim_phper/article/details/77581071