PHP匯出大量資料,儲存為CSV檔案

Yoooooo發表於2021-04-29

幾乎每個專案有大量匯出資料的需求,一口氣全部匯出消耗伺服器資源,甚至可以導致伺服器崩潰.

摘要

每個專案架構都不一樣,有的一抹(ma)胡,一個伺服器什麼都裝mysql, redis, memcached,這樣很省錢但是橫向擴充套件的時候就很頭疼.有的分散式部署,包括redis, mysql都是叢集方式,比如reids有很多叢集節點,mysql叢集讀寫分離,都可以根據自己需求橫向擴充套件.
CSV匯出大量資料限制瓶頸有很多,比如專案架構,伺服器配置,mysql配置,程式碼質量,sql語句等等.一抹(ma)胡式部署,本文可能改善不是很大,畢竟mysql還是要佔用伺服器資源,大家可以相互學習,共通進步.

程式碼部分

/**
* @description匯出CSV檔案
* @return
*/
public function exportCsv()
{
    /**
    * 第一步開啟一個檔案下載流
    * 這一步很重要,很多人都不知道這樣
    */
    $strFileName = date('Ymd');// 檔名 可以隨意
    ob_clean();//清理緩衝區 避免開頭出現空行
    $fp = fopen('php://output', 'a'); //開啟output流
    header('X-Accel-Buffering: no'); //直接下載檔案,這行可以不配置,會預設為4096k才會下載,
    header('Content-Type: application/octet-stream');
    header($this->changeFileNameWithAgent($strFileName)); //設定頭部檔名 解決非英文亂碼(中文/日文測試都可以)
    header("Content-type:text/csv;");

    /**
    * 第二步匯出資料,很簡單也很重要
    * 程式碼很簡單,第一反應 就這~~~~
    * 實際寫錯很容易伺服器崩潰
    */
    // 輸出第一行標題 (根據實際情況,可有可無)
    $arrayHeader = ["標題1", "標題2", "標題3", "標題4", "標題5"];

    // 轉義為預定字符集, 根據需求轉字符集(不需要轉化可以忽略該行程式碼),
    // 將內部字符集轉為SJIS-win,一般專案為UTF-8字符集
    // mb_internal_encoding() 設定或獲取內部字符集,有興趣的同學可以去看看
    mb_convert_variables('SJIS-win', mb_internal_encoding(), $arrayHeader);
    fputcsv($fp, $arrayHeader, ","); 
    //初始化必要引數
    $intOffset = 0; // mysql中offset引數
    $intPageSize = 20; // 查詢條數 根據實際情況更換
    $blnFinishedFlg = false;
    do {
        // 根據 $intOffset $intPageSize查詢資料庫,少量多次查詢
        $arrayData = [
            ['內容1-1', '內容1-2', '內容1-3', '內容1-4', ...],
            ['內容2-1', '內容2-2', '內容2-3', '內容2-4', ...],
            ['內容3-1', '內容3-2', '內容3-3', '內容3-4', ...],
            ...
        ];
        // 輸出到csv檔案
        foreach($arrayData as $item) {
            mb_convert_variables('SJIS-win', mb_internal_encoding(), $item);
            fputcsv($fp, $item, ",");
            ob_flush();
            flush();
        }
        if ($intPageSize === count($arrayData)) {
            // 獲取到的結果數量等於查詢條數,認為未取完資料繼續查詢, 修改offset值
            $intOffset = $intOffset + $intPageSize;
            $blnFinishedFlg = false;
        } else {
            //小於查詢條數,資料已取完
            $blnFinishedFlg = true;
        }
    } while (false === $blnFinishedFlg);

    /**
    * 第三步關閉檔案流
    */
    ob_end_flush();
    fclose($fp);
}

/**
* @description 對應不同瀏覽器、輸出漢字名出問題的Bug
* 由於IE Edge 包含Chrome等資訊、所以只能第一個判斷
* @param string $strFileName 檔名
* @return string Head資訊
*/
private function changeFileNameWithAgent($strFileName)
{
    $filename = preg_replace('/[\?\*\|\\/\:"><]/', '', $strFileName);
    $agent = $_SERVER['HTTP_USER_AGENT'];
    $strHeader = '';
    if (strpos($agent, "Edge")) {
        $filename = urlencode($filename);
        $filename = str_replace("+", "%20", $filename);
        $strHeader = "Content-Disposition: attachment; filename=" . $filename;
    } else if (strpos($agent, "Firefox")) {
        $filename = urlencode($filename);
        $filename = str_replace("+", "%20", $filename);
        $strHeader = 'Content-Disposition: attachment; filename*="utf8\'\'' . $filename . '"';
    } else if (strpos($agent, "Chrome")) {
        $strHeader = "Content-Disposition: attachment; filename=" . $filename;
    } else if (strpos($agent, "Safari")) {
        $strHeader = 'Content-Disposition: attachment;filename*=UTF-8\'\'' . rawurlencode($filename);
    } else {
        $filename = urlencode($filename);
        $filename = str_replace("+", "%20", $filename);
        $strHeader = "Content-Disposition: attachment; filename=" . $filename;
    }
    return $strHeader;
}

擴充

拿著匯出的csv檔案,這個時候有的人就要問了,為什麼我用excel開啟亂七八糟.
如果是開發人員問,wo giao!!,新員工可以理解,老員工可以勸退了.其實我也不知道為什麼會亂碼,可能跟那些奇怪的字符集有關係.作為一個正常的開發人員會用notepad++等編輯器開啟,檢視有沒有問題.一般不會有問題,如果這個有亂碼問題就需要修改字符集了.
但是有的人就開始犟嘴了,那客戶不懂程式碼啊,我又不能要求人家安裝編輯器呀!wo giao,內部出現奸細了.這種奸細行為的需求,你就可以理直氣壯的告訴他,滿足不了,因為你的字符集決定了他永遠都有可能亂碼,用UTF-8字符集,才有可能不亂碼.除非匯出包含bom的UTF-8格式,用excel開啟不會亂碼.

// BOM header UTF-8 
// 加在第一步後面
echo pack('C*',0xEF,0xBB,0xBF);

// 更換這個為UTF-8, 如果是uft8字符集可以刪除這兩行程式碼
mb_convert_variables('UTF-8', mb_internal_encoding(), $arrayHeader);
本作品採用《CC 協議》,轉載必須註明作者和本文連結

相關文章