PHP导出大量数据,保存为CSV文件

几乎每个项目有大量导出数据的需求,一口气全部导出消耗服务器资源,甚至可以导致服务器崩溃.

摘要

每个项目架构都不一样,有的一抹(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 协议》,转载必须注明作者和本文链接
《L04 微信小程序从零到发布》
从小程序个人账户申请开始,带你一步步进行开发一个微信小程序,直到提交微信控制台上线发布。
《L02 从零构建论坛系统》
以构建论坛项目 LaraBBS 为线索,展开对 Laravel 框架的全面学习。应用程序架构思路贴近 Laravel 框架的设计哲学。
讨论数量: 2

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!