几乎每个项目有大量导出数据的需求,一口气全部导出消耗服务器资源,甚至可以导致服务器崩溃.
摘要
每个项目架构都不一样,有的一抹(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); |