1. 程式人生 > >PHPExcel 大資料的匯出

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;
    }

複製程式碼

執行後資訊:

開啟檔案: