当前位置: 首页 > 后端技术 > PHP

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

时间:2023-03-29 23:35:43 PHP

最近接到一个请求,要求将选定时间段内对应的用户访问日志导出到excel中。由于用户量大,经常导出50万+数据的Condition。常用的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输出流,逐步往输出流中写入数据,一定时间后写入系统缓冲区冲入响应*避免缓冲区溢出*/publicfunctionarticleAccessLog($timeStart,$timeEnd){set_time_limit(0);$columns=['文章ID','文章标题',...];$csvFileName='用户日志'.$timeStart.'_'。$时间结束。'.xlsx';//设置告诉浏览器下载excel文件的headers('Content-Description:FileTransfer');header('Content-Type:application/vnd.ms-excel');header('Content-Disposition:attachment;filename="'.$fileName.'"');header('过期:0');header('Cache-Control:must-revalidate');header('Pragma:public');$fp=fopen('php://output','a');//打开输出流mb_convert_variables('GBK','UTF-8',$columns);fputcsv($fp,$columns);//将数据格式化成CSV格式写入输出流$accessNum='1000000'//从数据库中获取总量,假设是一百万$perSize=1000;//每次查询的个数$pages=ceil($accessNum/$perSize);$lastId=0;for($i=1;$i<=$pages;$i++){$accessLog=$logService->getArticleAccessLog($timeStart,$timeEnd,$lastId,$perSize);}foreach($accessLogas$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);出口();}嗯,其实很简单,就是一步步写好输出流,发送给浏览器,让浏览器一步步下载整个文件。由于是一步步写的,无法获取文件的整体大小,所以没办法通过settingheader("Content-Length:$size");下载前告诉浏览器文件有多大。不过不影响整体效果。这里的核心问题是解决大文件的实时生成和下载。更新:这里先说说我对数据库查询的思路,因为一步步写入EXCEL的数据其实是来自Mysql的分页查询。大家都知道它的语法是LIMIToffset,num。但是随着offset越来越大,Mysql会查询每一页需要跳过的行,这会严重影响Mysql的查询效率(包括MongoDB等NoSQL,不建议跳过多行获取)结果集),所以我使用LastId方法进行分页查询。类似于下面的语句:SELECTcolumnsFROM`table_name`WHERE`created_at`>='timerangestart'AND`created_at`<='timerangeend'AND`id`