PHPExcel 大資料的匯出
PHP交流群:294088839
Python交流群:652376983
PHPExcel 是一個php語言讀取匯出資料、匯入生成Excel的類庫,使用起來非常方便,但有時會遇到以些問題,比如匯出的資料超時,記憶體溢位等。
下面我們來說說這些問題和解決辦法。
PHPExcel 版本:@version 1.8.0, 2014-03-02
能遇到這樣的問題一般都是因為資料量大導致
1.PHPExcel 報錯
報錯提示:
'break' not in the 'loop' or 'switch' context
嚴格的講這個不是PHPExcel的錯誤,是PHP版本的問題,大於PHP5.6以後,“break”必須要在迴圈體內執行(for ,foreach, while, switch)
此處無迴圈,解決辦法:註釋掉break;
2.超時
提示:
Maximum execution time of 30 seconds exceeded
資料量過大,php執行超過30秒後就會報這樣的資訊
解決辦法:
可修改php.ini 或直接在執行頁面中新增
set_time_limit(0);
這樣就設定了php的執行超時
3.記憶體溢位
超時解決好之後,等待了好幾十秒後又來了個錯誤:
Allowed memory size of 134217728 bytes exhausted (tried to allocate 20480 bytes)
記憶體不足呀!
解決記憶體溢位我們分兩步走,
第一步:設定memory_limit
預設情況memory_limit 大小為100MB,當所需記憶體大於100MB就會溢位,所以設定足夠大的值
ini_set("memory_limit", "1024M"); // 根據電腦配置不夠繼續增加
第二步:設定PHPExcel單元格快取
單元格快取是將所需PHPExcel記憶體單元格物件快取到磁碟、memcache、MemoryGZip等,這樣讀取上會更耗時,但可以降低記憶體的消耗。
PHPExcel_CachedObjectStorageFactory 這個類中提供了這幾個單元格快取
const cache_in_memory = 'Memory'; const cache_in_memory_gzip = 'MemoryGZip'; #將單元格序列化後再進行Gzip壓縮,然後儲存在記憶體中 const cache_in_memory_serialized = 'MemorySerialized'; # 將單元格資料序列化後儲存在記憶體中 const cache_igbinary = 'Igbinary'; #儲存為緊密的二進位制形式 const cache_to_discISAM = 'DiscISAM'; #快取在臨時的磁碟檔案中,速度可能會慢一些 const cache_to_apc = 'APC'; #Alternative PHP Cache可選PHP快取 const cache_to_memcache = 'Memcache'; #儲存在memcache中 const cache_to_phpTemp = 'PHPTemp'; #儲存在php://temp const cache_to_wincache = 'Wincache'; const cache_to_sqlite = 'SQLite'; const cache_to_sqlite3 = 'SQLite3';
每一個worksheet都會有一個獨立的快取,當一個worksheet例項化時,就會根據設定或配置的快取方式來自動建立。一旦你開始讀取一個檔案或者你已經建立了第一個worksheet,就不能在改變快取的方式了。
- MemorySerialized: 使用這種快取方式,單元格會以序列化的方式儲存在記憶體中,這是降低記憶體使用率效能比較高的一種方案。
-
MemoryGZip: 與序列化的方式類似,這種方法在序列化之後,又進行gzip壓縮之後再放入記憶體中,這回跟進一步降低記憶體的使用,但是讀取和寫入時會有一些慢。
-
DiscISAM:當使用cache_to_discISAM這種方式時,所有的單元格將會儲存在一個臨時的磁碟檔案中,只把他們的在檔案中的位置儲存在PHP的記憶體中,這會比任何一種快取在記憶體中的方式都慢,但是能顯著的降低記憶體的使用。臨時磁碟檔案在指令碼執行結束是會自動刪除。
-
PHPTemp: 類 似cache_to_discISAM這種方式,使用cache_to_phpTemp時,所有的單元格會還存在php://temp I/O流中,只把 他們的位置儲存在PHP的記憶體中。PHP的php://memory包裹器將資料儲存在記憶體中,php://temp的行為類似,但是當儲存的資料大小超 過記憶體限制時,會將資料儲存在臨時檔案中,預設的大小是1MB,但是你可以在初始化時修改它。php://temp檔案在指令碼結束是會自動刪除。
4.大資料匯出
微軟的Excel設定單元格行數預設是6萬行rows,相對的講其實當我們超過1萬行的時候已經是大資料的匯出。
好比:有客戶10000人,平均每人每天產生10條活動記錄,要匯出上週所有的客戶活動記錄: 10000*10*7=700000
估計看70萬行的Excel這個人會瘋掉的,我們的建議是分批次匯出,按時間匯出到不同的excel
下面是一個PHPExcel官方的Demo(已修改過)
define('EOL', '<br />');
$objPHPExcel = new \app\extensions\PHPExcel\PHPExcel();
ini_set("memory_limit", "1024M"); // 設定php可使用記憶體
$cacheMethod = \PHPExcel_CachedObjectStorageFactory::cache_in_memory_gzip;
if (!\PHPExcel_Settings::setCacheStorageMethod($cacheMethod)) {
die($cacheMethod . " 快取方法不可用" . EOL);
}
echo date('H:i:s'), " 當前使用的快取方法是: ", $cacheMethod, " 方式", EOL;
echo date('H:i:s'), " 開始設定文件屬性", EOL;
$objPHPExcel->getProperties()->setCreator("Maarten Balliauw")
->setLastModifiedBy("Maarten Balliauw")
->setTitle("Office 2007 XLSX Test Document")
->setSubject("Office 2007 XLSX Test Document")
->setDescription("Test document for Office 2007 XLSX, generated using PHP classes.")
->setKeywords("office 2007 openxml php")
->setCategory("Test result file");
echo date('H:i:s'), " 開始新增單元格標題", EOL;
$objPHPExcel->setActiveSheetIndex(0);
$objPHPExcel->getActiveSheet()->setCellValue('A1', "Firstname");
$objPHPExcel->getActiveSheet()->setCellValue('B1', "Lastname");
$objPHPExcel->getActiveSheet()->setCellValue('C1', "Phone");
$objPHPExcel->getActiveSheet()->setCellValue('D1', "Fax");
$objPHPExcel->getActiveSheet()->setCellValue('E1', "Is Client ?");
// 設定單元格寬度
$objPHPExcel->getActiveSheet()->getColumnDimension('A')->setAutoSize(true);
$objPHPExcel->getActiveSheet()->getColumnDimension('B')->setWidth(50);
/**
* 左對齊與 右對齊
* 可設定整列->getStyle('N') 可針對行rows設定getStyle('N3')
*/
$objPHPExcel->getActiveSheet()->getStyle('B')->getAlignment()->setHorizontal(\PHPExcel_Style_Alignment::HORIZONTAL_RIGHT);
$objPHPExcel->getActiveSheet()->getStyle('B3')->getAlignment()->setHorizontal(\PHPExcel_Style_Alignment::HORIZONTAL_LEFT);
echo date('H:i:s'), " 設定隱藏C D列", EOL;
$objPHPExcel->getActiveSheet()->getColumnDimension('C')->setVisible(false);
$objPHPExcel->getActiveSheet()->getColumnDimension('D')->setVisible(false);
echo date('H:i:s'), " 設定大綱級別", EOL;
$objPHPExcel->getActiveSheet()->getColumnDimension('E')->setOutlineLevel(1)
->setVisible(false)
->setCollapsed(true);
/**
* 開始新增資料
*/
for ($i = 2; $i <= 50000; $i++) {
$objPHPExcel->getActiveSheet()->setCellValue('A' . $i, "FName $i")
->setCellValue('B' . $i, "LName $i")
->setCellValue('C' . $i, "PhoneNo $i")
->setCellValue('D' . $i, "FaxNo $i")
->setCellValue('E' . $i, true);
}
$objPHPExcel->getActiveSheet()->setTitle('供應商資訊');
echo date('H:i:s'), " 設定格式為Excel2007版格式", EOL;
$callStartTime = microtime(true);
$objWriter = \PHPExcel_IOFactory::createWriter($objPHPExcel, 'Excel2007');
$objWriter->save(str_replace('.php', '.xlsx', __FILE__));
$callEndTime = microtime(true);
$callTime = $callEndTime - $callStartTime;
echo date('H:i:s'), " 設定生成的檔案為: ", str_replace('.php', '.xlsx', pathinfo(__FILE__, PATHINFO_BASENAME)), EOL;
echo date('H:i:s'), ' 寫入Workbook中耗時 ', sprintf('%.4f', $callTime), " 秒", EOL;
echo date('H:i:s'), ' 當前記憶體使用情況: ', (memory_get_usage(true) / 1024 / 1024), " MB", EOL;
echo date('H:i:s'), " 記憶體使用峰值: ", (memory_get_peak_usage(true) / 1024 / 1024), " MB", EOL;
echo date('H:i:s'), " 完成寫入檔案", EOL;
echo date('H:i:s'), ' 檔案被建立在: ', getcwd(), '目錄', EOL;
下面模擬一個大資料的匯出:
msyql中tcustomer表有資料4萬多條
我們設定
set_time_limit(0);
ini_set("memory_limit", "1024M");
\PHPExcel_CachedObjectStorageFactory::cache_in_memory_gzip; # 單元格快取為MemoryGZip
然後匯出所有4萬多條資料
程式碼如下(Yii2)
/**
* PHPExcel 資料匯出
*/
public function actionPhpexcel() {
define('EOL', '<br />');
$objPHPExcel = new \app\extensions\PHPExcel\PHPExcel();
ini_set("memory_limit", "1024M"); // 設定php可使用記憶體
set_time_limit(0); # 設定執行時間最大值
$cacheMethod = \PHPExcel_CachedObjectStorageFactory::cache_in_memory_gzip;
if (!\PHPExcel_Settings::setCacheStorageMethod($cacheMethod)) {
die($cacheMethod . " 快取方法不可用" . EOL);
}
echo date('H:i:s'), " 當前使用的快取方法是: ", $cacheMethod, " 方式", EOL;
echo date('H:i:s'), " 開始設定文件屬性", EOL;
$objPHPExcel->getProperties()->setCreator("dcb3688")
->setLastModifiedBy("dcb3688")
->setTitle("客戶資訊記錄")
->setSubject("客戶資訊Document")
->setDescription("描述……")
->setKeywords("office 2007 php")
->setCategory("產品資訊AAA");
echo date('H:i:s'), " 開始新增單元格標題", EOL;
$objPHPExcel->setActiveSheetIndex(0);
$objPHPExcel->getActiveSheet()->setCellValue('A1', "客戶姓名");
$objPHPExcel->getActiveSheet()->setCellValue('B1', "性別");
$objPHPExcel->getActiveSheet()->setCellValue('C1', "Province");
$objPHPExcel->getActiveSheet()->setCellValue('D1', "City");
$objPHPExcel->getActiveSheet()->setCellValue('E1', "Town");
$objPHPExcel->getActiveSheet()->setCellValue('F1', "Telephone");
$objPHPExcel->getActiveSheet()->setCellValue('G1', "屬相");
$objPHPExcel->getActiveSheet()->setCellValue('H1', "星座");
$objPHPExcel->getActiveSheet()->setCellValue('I1', "血型");
$objPHPExcel->getActiveSheet()->setCellValue('J1', "Nid");
$objPHPExcel->getActiveSheet()->setCellValue('K1', "Uid");
$objPHPExcel->getActiveSheet()->setCellValue('L1', "Etime");
$objPHPExcel->getActiveSheet()->setCellValue('M1', "Regtime");
$objPHPExcel->getActiveSheet()->setCellValue('N1', "Signup");
$objPHPExcel->getActiveSheet()->setCellValue('O1', "經度");
$objPHPExcel->getActiveSheet()->setCellValue('P1', "緯度");
$objPHPExcel->getActiveSheet()->setCellValue('Q1', "型別");
$objPHPExcel->getActiveSheet()->setCellValue('R1', "狀態");
/**
* 單元格寬度
*/
$objPHPExcel->getActiveSheet()->getColumnDimension('F')->setAutoSize(true);
$objPHPExcel->getActiveSheet()->getColumnDimension('J')->setWidth(45);
$objPHPExcel->getActiveSheet()->getColumnDimension('L')->setAutoSize(true);
$objPHPExcel->getActiveSheet()->getColumnDimension('M')->setAutoSize(true);
$objPHPExcel->getActiveSheet()->getColumnDimension('N')->setAutoSize(true);
/**
* 左對齊與 右對齊
* 可設定整列->getStyle('N') 可針對行rows設定getStyle('N3')
*/
$objPHPExcel->getActiveSheet()->getStyle('N')->getAlignment()->setHorizontal(\PHPExcel_Style_Alignment::HORIZONTAL_RIGHT);
$objPHPExcel->getActiveSheet()->getStyle('N3')->getAlignment()->setHorizontal(\PHPExcel_Style_Alignment::HORIZONTAL_LEFT);
#####################################開始新增資料###############################################################
/**
* 分頁分時間: 微軟Execl最大值6萬行, total/60000=檔案個數, limit 60000, 60000
* $model->find()->offset($pages->offset)->limit(60000)->all()
*/
$model = \app\models\Tcustomer::find()->orderBy('regtime desc')->all();
if (!empty($model)) {
foreach ($model as $key => $value) {
$Cellkey = $key + 2;
$blood = [1 => 'A型', 2 => 'B型', 3 => 'AB型', 4 => 'O型'];
$objPHPExcel->getActiveSheet()->setCellValue('A' . $Cellkey, mb_substr($value->realname, 0, -1) . '*');
$objPHPExcel->getActiveSheet()->setCellValue('B' . $Cellkey, rand(1, 2) == 1 ? '先生' : '女士');
$objPHPExcel->getActiveSheet()->setCellValue('C' . $Cellkey, $value->province);
$objPHPExcel->getActiveSheet()->setCellValue('D' . $Cellkey, $value->city);
$objPHPExcel->getActiveSheet()->setCellValue('E' . $Cellkey, $value->town);
$objPHPExcel->getActiveSheet()->setCellValue('F' . $Cellkey, $value->telephone ? substr($value->telephone, 0, 3) . '*********' : '');
$objPHPExcel->getActiveSheet()->setCellValue('G' . $Cellkey, $value->sx);
$objPHPExcel->getActiveSheet()->setCellValue('H' . $Cellkey, $value->constel);
$objPHPExcel->getActiveSheet()->setCellValue('I' . $Cellkey, $blood[array_rand($blood)]);
$objPHPExcel->getActiveSheet()->setCellValue('J' . $Cellkey, mb_substr($value->nid, 0, -3));
$objPHPExcel->getActiveSheet()->setCellValue('K' . $Cellkey, $value->uid);
$objPHPExcel->getActiveSheet()->setCellValue('L' . $Cellkey, $value->exp);
$objPHPExcel->getActiveSheet()->setCellValue('M' . $Cellkey, $value->regtime);
$objPHPExcel->getActiveSheet()->setCellValue('N' . $Cellkey, $value->signup ? $value->signup : '無資料');
$objPHPExcel->getActiveSheet()->setCellValue('O' . $Cellkey, substr($value->lng, 0, 4) . rand(10000, 999999));
$objPHPExcel->getActiveSheet()->setCellValue('P' . $Cellkey, substr($value->lat, 0, 3) . rand(10000, 999999));
$objPHPExcel->getActiveSheet()->setCellValue('Q' . $Cellkey, $value->type == 1 ? '意向客戶' : '觀望中客戶');
$objPHPExcel->getActiveSheet()->setCellValue('R' . $Cellkey, $value->status == 1 ? '已下單' : '已跟進');
}
} else {
die(" 暫無資料" . EOL);
}
#######################################################################################################
$objPHPExcel->getActiveSheet()->setTitle('客戶資訊');
echo date('H:i:s'), " 設定格式為Excel2007版格式", EOL;
$callStartTime = microtime(true);
$objWriter = \PHPExcel_IOFactory::createWriter($objPHPExcel, 'Excel2007');
$objWriter->save(str_replace('.php', '.xlsx', __FILE__));
$callEndTime = microtime(true);
$callTime = $callEndTime - $callStartTime;
echo date('H:i:s'), " 設定生成的檔案為: ", str_replace('.php', '.xlsx', pathinfo(__FILE__, PATHINFO_BASENAME)), EOL;
echo date('H:i:s'), ' 寫入Workbook中耗時 ', sprintf('%.4f', $callTime), " 秒", EOL;
echo date('H:i:s'), ' 當前記憶體使用情況: ', (memory_get_usage(true) / 1024 / 1024), " MB", EOL;
echo date('H:i:s'), " 記憶體使用峰值: ", (memory_get_peak_usage(true) / 1024 / 1024), " MB", EOL;
echo date('H:i:s'), " 完成寫入檔案", EOL;
echo date('H:i:s'), ' 檔案被建立在: ', getcwd(), '目錄', EOL;
}
執行後資訊:
開啟檔案: