• 技术文章 >后端开发 >php教程

    PHP实时生成并下载超大数据量的EXCEL文件

    藏色散人藏色散人2019-09-12 17:56:22转载1386
    最近接到一个需求,通过选择的时间段导出对应的用户访问日志到excel中, 由于用户量较大,经常会有导出50万加数据的情况。

    而常用的PHPexcel包需要把所有数据拿到后才能生成excel, 在面对生成超大数据量的excel文件时这显然是会造成内存溢出的,所以考虑使用让PHP边写入输出流边让浏览器下载的形式来完成需求。

    我们通过如下的方式写入PHP输出流

    $fp = fopen('php://output', 'a');
    fputs($fp, 'strings');
    ....
    ....
    fclose($fp)

    php://output是一个可写的输出流,允许程序像操作文件一样将输出写入到输出流中,PHP会把输出流中的内容发送给web服务器并返回给发起请求的浏览器

    另外由于excel数据是从数据库里逐步读出然后写入输出流的所以需要将PHP的执行时间设长一点(默认30秒)set_time_limit(0)不对PHP执行时间做限制。

    注:

    以下代码只是阐明生成大数据量EXCEL的思路和步骤,并且在去掉项目业务代码后程序有语法错误不能拿来直接运行,请根据自己的需求填充对应的业务代码!

    /**
         * 文章访问日志
         * 下载的日志文件通常很大, 所以先设置csv相关的Header头, 然后打开
         * PHP output流, 渐进式的往output流中写入数据, 写到一定量后将系统缓冲冲刷到响应中
         * 避免缓冲溢出
         */
        public function articleAccessLog($timeStart, $timeEnd)
        {
            set_time_limit(0);
            $columns = [
                '文章ID', '文章标题', ......
            ];
            $csvFileName = '用户日志' . $timeStart .'_'. $timeEnd . '.xlsx';
            //设置好告诉浏览器要下载excel文件的headers
            header('Content-Description: File Transfer');
            header('Content-Type: application/vnd.ms-excel');
            header('Content-Disposition: attachment; filename="'. $fileName .'"');
            header('Expires: 0');
            header('Cache-Control: must-revalidate');
            header('Pragma: public');
            $fp = fopen('php://output', 'a');//打开output流
            mb_convert_variables('GBK', 'UTF-8', $columns);
            fputcsv($fp, $columns);//将数据格式化为CSV格式并写入到output流中
            $accessNum = '1000000'//从数据库获取总量,假设是一百万
            $perSize = 1000;//每次查询的条数
            $pages   = ceil($accessNum / $perSize);
            $lastId  = 0;
            for($i = 1; $i <= $pages; $i++) {
                $accessLog = $logService->getArticleAccessLog($timeStart, $timeEnd, $lastId, $perSize);
                foreach($accessLog as $access) {
                    $rowData = [
                        ......//每一行的数据
                    ];
                    mb_convert_variables('GBK', 'UTF-8', $rowData);
                    fputcsv($fp, $rowData);
                    $lastId = $access->id;
                }
                unset($accessLog);//释放变量的内存
                //刷新输出缓冲到浏览器
                ob_flush();
                flush();//必须同时使用 ob_flush() 和flush() 函数来刷新输出缓冲。
            }
            fclose($fp);
            exit();
        }

    好了, 其实很简单,就是用逐步写入输出流并发送到浏览器让浏览器去逐步下载整个文件,由于是逐步写入的无法获取文件的总体size所以就没办法通过设置header("Content-Length: $size");在下载前告诉浏览器这个文件有多大了。不过不影响整体的效果这里的核心问题是解决大文件的实时生成和下载。

    更新: 说一下我数据库查询这里的思路,因为逐步写入EXCEL的数据实际上来自Mysql的分页查询,大家知道其语法是LIMIT offset, num 不过随着offset越来越大Mysql在每次分页查询时需要跳过的行数就越多,这会严重影响Mysql查询的效率(包括MongoDB这样的NoSQL也是不建议skip掉多条来取结果集),所以我采用LastId的方式来做分页查询。

    类似下面的语句:

    SELECT columns FROM `table_name` 
    WHERE `created_at` >= 'time range start' 
    AND `created_at` <= 'time range end' 
    AND  `id` < LastId 
    ORDER BY `id` DESC 
    LIMIT num

    以上就是PHP实时生成并下载超大数据量的EXCEL文件的详细内容,更多请关注php中文网其它相关文章!

    声明:本文转载于:segmentfault,如有侵犯,请联系admin@php.cn删除
    专题推荐:PHP EXCEL
    上一篇:php://output和php://stdout的区别 下一篇:利用for循环实现excel中多列数据合并到一个单元格
    PHP编程就业班

    相关文章推荐

    • 在php当中常量和变量的区别• 有关PHP中PDO连接数据库的详细教程与实际操作演示• php编译安装扩展redis及swoole• php://output和php://stdout的区别

    全部评论我要评论

  • 离歌浅唱醉人心丶

    很有用啊,感谢

    2019-08-28

  • 离歌浅唱醉人心丶

    很有用啊,感谢

    2019-08-28

  • 离歌浅唱醉人心丶

    很有用啊,感谢

    2019-08-28

  • 离歌浅唱醉人心丶

    很有用啊,感谢

    2019-08-28

  • 离歌浅唱醉人心丶

    很有用啊,感谢

    2019-08-28

  • 离歌浅唱醉人心丶

    很有用啊,感谢

    2019-08-28

  • 光速机*

    几百M的excel得下载十多分钟,你们经理允许http连接维持这么长时间吗

    2020-10-25

  • 光速机*

    我感觉一个合适的思路 应该是先写服务器文件生成exel,然后在做断点下载.按你的思路nginx就挂了

    2020-10-25

  • 取消发布评论发送
  • 1/1

    PHP中文网