準(zhǔn)備:
1、PHP設(shè)置坑:
set_time_limit – 設(shè)置腳本最大執(zhí)行時(shí)間:
此配置一般PHP默認(rèn)是30秒,如果你是數(shù)據(jù)小的,可能就不會(huì)發(fā)現(xiàn)有該設(shè)置問(wèn)題,但如果你數(shù)據(jù)達(dá)到了百萬(wàn)級(jí)導(dǎo)出,往往30秒是不夠的,因此你需要在你的腳本中添加 set_time_limit(0),讓該腳本沒(méi)有執(zhí)行時(shí)間現(xiàn)在
memory_limit – PHP的內(nèi)存限定:
此配置一般php默認(rèn)是128M,如果之前做過(guò)小數(shù)據(jù)的朋友可能也會(huì)動(dòng)過(guò)這個(gè)配置就能解決許多問(wèn)題,或許有人想,你大數(shù)據(jù)也把這個(gè)調(diào)大不就行了嗎?那么真的是too young too native了,你本地能設(shè)置1G或者無(wú)限制或許真的沒(méi)問(wèn)題,但是正式場(chǎng),你這么搞遲早會(huì)出事的,一個(gè)PHP程序占那么大的內(nèi)存的空間,如果你叫你公司運(yùn)維幫忙調(diào)一下配置,估計(jì)運(yùn)維一定很不情愿,服務(wù)器硬件這么搞也是太奢侈了。所以說(shuō),我們要盡量避免調(diào)大該設(shè)置。
2、excel坑:
既然是導(dǎo)出數(shù)據(jù),大伙們當(dāng)然馬上想到了excel格式了,多方便查看數(shù)據(jù)呀,然而萬(wàn)萬(wàn)沒(méi)想到excel也是有脾氣的呀!
表數(shù)據(jù)限制:
1、Excel 2003及以下的版本。一張表最大支持65536行數(shù)據(jù),256列。
2、Excel 2007-2010版本。一張表最大支持1048576行,16384列。
也就是說(shuō)你想幾百萬(wàn)條輕輕松松一次性導(dǎo)入一張EXCEL表是不行的,你起碼需要進(jìn)行數(shù)據(jù)分割,保證數(shù)據(jù)不能超過(guò)104W一張表。
PHPexcel內(nèi)存溢出:
既然數(shù)據(jù)限制在104W,那么數(shù)據(jù)分割就數(shù)據(jù)分割唄,于是你嘗試50W一次導(dǎo)入表,然而PHPexcel內(nèi)部有函數(shù)報(bào)內(nèi)存溢出錯(cuò)誤,然后你就不斷的調(diào)小數(shù)據(jù)量,直到5W一次導(dǎo)入你都會(huì)發(fā)現(xiàn)有內(nèi)存溢出錯(cuò)誤。這是為什么呢,雖然你分割數(shù)據(jù)來(lái)導(dǎo)入多個(gè)數(shù)據(jù)表,但是最后PHPexcel內(nèi)部還是一次性把所有表數(shù)據(jù)放進(jìn)一個(gè)變量中來(lái)創(chuàng)建文件……額,這幾百萬(wàn)數(shù)據(jù)一個(gè)變量存儲(chǔ),你想內(nèi)存不溢出,還真有點(diǎn)困難。
(后來(lái)看了一些文章發(fā)現(xiàn)PHPExcel也有解決方案,PHPExcel_Settings::setCacheStorageMethod方法更改緩沖方式來(lái)減小內(nèi)存的使用)
3、csv坑:
EXCEL這么麻煩,我不用還不行嗎?我用csv文件儲(chǔ)存,既不限制數(shù)量,還能直接用EXCEL來(lái)查看,又能以后把文件導(dǎo)入數(shù)據(jù)庫(kù),一舉幾得豈不是美哉?咦,少俠好想法!但是CSV也有坑哦!
輸出buffer過(guò)多:
當(dāng)你用PHP原生函數(shù)putcsv()其實(shí)就使用到了輸出緩存buffer,如果你把幾百萬(wàn)的數(shù)據(jù)一直用這個(gè)函數(shù)輸出,會(huì)導(dǎo)致輸出緩存太大而報(bào)錯(cuò)的,因此我們每隔一定量的時(shí)候,必須進(jìn)行將輸出緩存中的內(nèi)容取出來(lái),設(shè)置為等待輸出狀態(tài)。具體操作是:
1、ob_flush();
2、flush();
具體說(shuō)明介紹:PHP flush()與ob_flush()的區(qū)別詳解
EXCEL查看CSV文件數(shù)量限制:
大多數(shù)人看csv文件都是直接用EXCEL打開(kāi)的。額,這不就是回到EXCEL坑中了嗎?EXCEL有數(shù)據(jù)顯示限制呀,你幾百萬(wàn)數(shù)據(jù)只給你看104W而已。什么?你不管?那是他們打開(kāi)方式不對(duì)而已?不好不好,我們解決也不難呀,我們也把數(shù)據(jù)分割一下就好了,再分開(kāi)csv文件保存,反正你不分割數(shù)據(jù)變量也會(huì)內(nèi)存溢出。
4、總結(jié)做法
分析完上面那些坑,那么我們的解決方案來(lái)了,假設(shè)數(shù)據(jù)量是幾百萬(wàn)。
1、那么我們要從數(shù)據(jù)庫(kù)中讀取要進(jìn)行數(shù)據(jù)量分批讀取,以防變量?jī)?nèi)存溢出,
2、我們選擇數(shù)據(jù)保存文件格式是csv文件,以方便導(dǎo)出之后的閱讀、導(dǎo)入數(shù)據(jù)庫(kù)等操作。
3、以防不方便excel讀取csv文件,我們需要104W之前就得把數(shù)據(jù)分割進(jìn)行多個(gè)csv文件保存
4、多個(gè)csv文件輸出給用戶下載是不友好的,我們還需要把多個(gè)csv文件進(jìn)行壓縮,最后提供給一個(gè)ZIP格式的壓縮包給用戶下載就好。
代碼:
//導(dǎo)出說(shuō)明:因?yàn)镋XCEL單表只能顯示104W數(shù)據(jù),同時(shí)使用PHPEXCEL容易因?yàn)閿?shù)據(jù)量太大而導(dǎo)致占用內(nèi)存過(guò)大,
//因此,數(shù)據(jù)的輸出用csv文件的格式輸出,但是csv文件用EXCEL軟件讀取同樣會(huì)存在只能顯示104W的情況,所以將數(shù)據(jù)分割保存在多個(gè)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ù)庫(kù)取100000條以防變量緩存太大
// 每隔$limit行,刷新一下輸出buffer,不要太大,也不要太小
$limit = 100000;
// buffer計(jì)數(shù)器
$cnt = 0;
$fileNameArr = array();
// 逐行取出數(shù)據(jù),不浪費(fèi)內(nèi)存
for ($i = 0; $i < ceil($sqlCount / $sqlLimit); $i++) {
$fp = fopen($mark . '*' . $i . '.csv', 'w'); //生成臨時(shí)文件
// chmod('attack_ip*info*' . $i . '.csv',777);//修改可執(zhí)行權(quán)限
$fileNameArr[] = $mark . '_' . $i . '.csv';
// 將數(shù)據(jù)通過(guò)fputcsv寫到文件句柄
fputcsv($fp, $head);
$dataArr = $data->offset($i * $sqlLimit)->limit($sqlLimit)->get()->toArray();
foreach ($dataArr as $a) {
$cnt++;
if ($limit == $cnt) {
//刷新一下輸出buffer,防止由于數(shù)據(jù)過(guò)多造成問(wèn)題
ob_flush();
flush();
$cnt = 0;
}
fputcsv($fp, $a);
}
fclose($fp); //每生成一個(gè)文件關(guān)閉
}
//進(jìn)行多個(gè)文件壓縮
$zip = new ZipArchive();
$filename = $mark . ".zip";
$zip->open($filename, ZipArchive::CREATE); //打開(kāi)壓縮包
foreach ($fileNameArr as $file) {
$zip->addFile($file, basename($file)); //向壓縮包中添加文件
}
$zip->close(); //關(guān)閉壓縮包
foreach ($fileNameArr as $file) {
unlink($file); //刪除csv臨時(shí)文件
}
//輸出壓縮文件提供下載
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); //刪除壓縮包臨時(shí)文件
}
總結(jié):
其實(shí)上面代碼還是有優(yōu)化的空間的,比如說(shuō)用異常捕捉,以防因?yàn)槟承╁e(cuò)誤而導(dǎo)致生成了一些臨時(shí)文件又沒(méi)有正常刪除,還有PHPexcel的緩存設(shè)置也許能解決內(nèi)存溢出問(wèn)題,可以生成一個(gè)EXCEL文件多個(gè)工作表的形式,這樣對(duì)于文件閱讀者來(lái)說(shuō)更友好。
原文來(lái)源:https://blog.csdn.net/tim_phper/article/details/77581071