php thinkphp5.1 导出百万数据方案
先说一下一般我们在执行大数据操作时,遇到最常见的问题就是:
1、执行超时 参考这里
2、内存溢出 参考这里
看了这两个之后再来看这个方案,实现思路大概是:
- 先利用set_time_limit(0)不限制执行时间
- 将要查询的数据分段查询,每次查询创建一个csv临时文件,每次清空缓冲区
- 数据全部写完之后将所有csv文件合并成一个csv文件
- 合并之后利用zip扩展进行打包下载(如果没有安装此扩展,请先去安装扩展)
- 最后将所有生成的文件删除
安装扩展,PHP开启自带的ZipArchive类,实现压缩解压功能
Windows环境:
-
首先需要从官网上下载,下载地址 https://windows.php.net/downloads/pecl/releases/zip/
-
打开官网列表后需要查找适合自己的PHP版本和系统的zip,我的PHP版本是5.5的,这里我选择的版本号是1.13.5
-
下载完后解压,把里面的php_zip.dll文件放到PHP的扩展文件夹里
扩展文件夹路径一般都是在PHP版本文件夹里的ext文件夹,譬如我的就是D:\php-5.5.38\ext
- 把php_zip.dll文件放进去后,打开PHP的配置文件php.ini,添加extension=php_zip.dll,保存后,重启apache服务器
Linux环境:
1、在Linux下没有php_zip.dll这个文件(有也不会起作用的),所以需要重新编译一下php的zip模块。具体安装方法如下:
cd /usr/src
wget http://pecl.php.net/get/zip
tar -zxvf zip
cd zip-1.x.x
phpize
./configure
make
sudo make instal
其中, 在最后使用make install命令的时候,可能需要用到root的权限,所以建议使用sudo来运行。安装完之后,屏幕上会提示zip.so的位置。然后将其记录下来,如:/usr/local/lib/php/extensions/zip.so。
2、使用root权限修改php.ini(通常可能会在/usr/local/lib/文件夹下,不过视当初安装php而定,可以通过phpinfo()来查看):
增加extension = /usr/local/lib/php/extensions/zip.so,然后同样在php.ini文件中,将 zlib.output_compression = Off 改为 zlib.output_compression = On ;
3、最后别忘了重启一下Apache:apachectl restart;
这个针对php的zip模块就安装完成了,能够在php中使用ZipArchive类了。
/** * 百万级数据导出 */ public function excelout(){ //不限制执行时间,以防超时 set_time_limit(0); //文件名 $xlsName = '名字'.date('Ymd His'); //统计总行数 $sqlCount = 0; //表头 $xlsCell = ['姓名','性别','年龄','电话','QQ','地址']; //对应表头的字段 $fields = 'name,sex,age,tel,qq,address'; //统计总行数 $sqlCount = UserModel::count(); //每次取多少条 $sqlLimit = 2000;//每次只从数据库取2000条 // buffer计数器 $cnt = 0; $fileNameArr = array(); //分段执行,以免内存写满 for ($i = 0; $i < ceil($sqlCount / $sqlLimit); $i++) { $fp = fopen($xlsName . '_' . $i . '.csv', 'w'); //生成临时文件 $fileNameArr[] = $xlsName . '_' . $i . '.csv';//将临时文件保存起来 //第一次执行时将表头写入 if($i == 0){ fputcsv($fp, $xlsCell); } //查询出数据 $xlsData = UserModel::field($fields) ->limit($i * $sqlLimit,$sqlLimit) ->select()->toArray(); foreach ($xlsData as $k=>$v) { $cnt++; //执行下一次循环之前清空缓冲区 if ($sqlLimit == $cnt) { ob_flush(); $cnt = 0; } //每行写入到临时文件 fputcsv($fp, $v); } fclose($fp); //每生成一个文件关闭 } //将所有临时文件合并成一个 foreach ($fileNameArr as $file){ //如果是文件,提出文件内容,写入目标文件 if(is_file($file)){ $fileName = $file; //打开临时文件 $handle1 = fopen($fileName,'r'); //读取临时文件 if($str = fread($handle1,filesize($fileName))){ //关闭临时文件 fclose($handle1); //打开或创建要合并成的文件,往末尾插入的方式添加内容并保存 $handle2 = fopen($xlsName.'.csv','a+'); //写入内容 if(fwrite($handle2, $str)){ //关闭合并的文件,避免浪费资源 fclose($handle2); } } } } //将文件压缩,避免文件太大,下载慢 $zip = new \ZipArchive(); $filename = $xlsName . ".zip"; $zip->open($filename, \ZipArchive::CREATE); //打开压缩包 $zip->addFile($xlsName.'.csv', basename($xlsName.'.csv')); //向压缩包中添加文件 $zip->close(); //关闭压缩包 foreach ($fileNameArr as $file) { unlink($file); //删除csv临时文件 } //输出压缩文件提供下载 header("Cache-Control: max-age=0"); header("Content-Description: File Transfer"); header('Content-disposition: attachment; filename=' . basename($filename)); // 文件名 header("Content-Type: application/zip"); // zip格式的 header("Content-Transfer-Encoding: binary"); // header('Content-Length: ' . filesize($filename)); // @readfile($filename);//输出文件; unlink($filename); //删除压缩包临时文件 unlink($xlsName.'.csv'); //删除合并的临时文件 }