先說一下一般我們在執行大數據操作時,遇到最常見的問題就是:
1、執行超時 參考這里
2、內存溢出 參考這里
看了這兩個之后再來看這個方案,實現思路大概是:
- 先利用set_time_limit(0)不限制執行時間
- 將要查詢的數據分段查詢,每次查詢創建一個csv臨時文件,每次清空緩沖區
- 數據全部寫完之后將所有csv文件合並成一個csv文件
- 合並之后利用zip擴展進行打包下載(如果沒有安裝此擴展,請先去安裝擴展)
- 最后將所有生成的文件刪除
安裝擴展,PHP開啟自帶的ZipArchive類,實現壓縮解壓功能
Windows環境:
-
首先需要從官網上下載,下載地址 https://windows.php.net/downloads/pecl/releases/zip/
-
打開官網列表后需要查找適合自己的PHP版本和系統的zip,我的PHP版本是5.5的,這里我選擇的版本號是1.13.5
-
下載完后解壓,把里面的php_zip.dll文件放到PHP的擴展文件夾里
擴展文件夾路徑一般都是在PHP版本文件夾里的ext文件夾,譬如我的就是D:\php-5.5.38\ext
- 把php_zip.dll文件放進去后,打開PHP的配置文件php.ini,添加extension=php_zip.dll,保存后,重啟apache服務器
Linux環境:
1、在Linux下沒有php_zip.dll這個文件(有也不會起作用的),所以需要重新編譯一下php的zip模塊。具體安裝方法如下:
cd /usr/src
wget http://pecl.php.net/get/zip
tar -zxvf zip
cd zip-1.x.x
phpize
./configure
make
sudo make instal
其中, 在最后使用make install命令的時候,可能需要用到root的權限,所以建議使用sudo來運行。安裝完之后,屏幕上會提示zip.so的位置。然后將其記錄下來,如:/usr/local/lib/php/extensions/zip.so。
2、使用root權限修改php.ini(通常可能會在/usr/local/lib/文件夾下,不過視當初安裝php而定,可以通過phpinfo()來查看):
增加extension = /usr/local/lib/php/extensions/zip.so,然后同樣在php.ini文件中,將 zlib.output_compression = Off 改為 zlib.output_compression = On ;
3、最后別忘了重啟一下Apache:apachectl restart;
這個針對php的zip模塊就安裝完成了,能夠在php中使用ZipArchive類了。
/** * 百萬級數據導出 */ public function excelout(){ //不限制執行時間,以防超時 set_time_limit(0); //文件名 $xlsName = '名字'.date('Ymd His'); //統計總行數 $sqlCount = 0; //表頭 $xlsCell = ['姓名','性別','年齡','電話','QQ','地址']; //對應表頭的字段 $fields = 'name,sex,age,tel,qq,address'; //統計總行數 $sqlCount = UserModel::count(); //每次取多少條 $sqlLimit = 2000;//每次只從數據庫取2000條 // buffer計數器 $cnt = 0; $fileNameArr = array(); //分段執行,以免內存寫滿 for ($i = 0; $i < ceil($sqlCount / $sqlLimit); $i++) { $fp = fopen($xlsName . '_' . $i . '.csv', 'w'); //生成臨時文件 $fileNameArr[] = $xlsName . '_' . $i . '.csv';//將臨時文件保存起來 //第一次執行時將表頭寫入 if($i == 0){ fputcsv($fp, $xlsCell); } //查詢出數據 $xlsData = UserModel::field($fields) ->limit($i * $sqlLimit,$sqlLimit) ->select()->toArray(); foreach ($xlsData as $k=>$v) { $cnt++; //執行下一次循環之前清空緩沖區 if ($sqlLimit == $cnt) { ob_flush(); $cnt = 0; } //每行寫入到臨時文件 fputcsv($fp, $v); } fclose($fp); //每生成一個文件關閉 } //將所有臨時文件合並成一個 foreach ($fileNameArr as $file){ //如果是文件,提出文件內容,寫入目標文件 if(is_file($file)){ $fileName = $file; //打開臨時文件 $handle1 = fopen($fileName,'r'); //讀取臨時文件 if($str = fread($handle1,filesize($fileName))){ //關閉臨時文件 fclose($handle1); //打開或創建要合並成的文件,往末尾插入的方式添加內容並保存 $handle2 = fopen($xlsName.'.csv','a+'); //寫入內容 if(fwrite($handle2, $str)){ //關閉合並的文件,避免浪費資源 fclose($handle2); } } } } //將文件壓縮,避免文件太大,下載慢 $zip = new \ZipArchive(); $filename = $xlsName . ".zip"; $zip->open($filename, \ZipArchive::CREATE); //打開壓縮包 $zip->addFile($xlsName.'.csv', basename($xlsName.'.csv')); //向壓縮包中添加文件 $zip->close(); //關閉壓縮包 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); //刪除壓縮包臨時文件 unlink($xlsName.'.csv'); //刪除合並的臨時文件 }