幾乎每個專案有大量匯出資料的需求,一口氣全部匯出消耗伺服器資源,甚至可以導致伺服器崩潰.
摘要
每個專案架構都不一樣,有的一抹(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 協議》,轉載必須註明作者和本文連結