使用生成器yield导出百万数据

设备

centos 1核2G

成果

10W【10列】 数据导出 数据大小20M 左右,时间花费5分钟
50W【10列】 数据导出 数据大小96M 左右,花费12分钟
100W【10列】 数据导出 数据大小192M 左右,花费23分钟

注意的坑
excel坑

既然是导出数据,大伙们当然马上想到了excel格式了,多方便查看数据呀,然而万万没想到excel也是有脾气的呀! 

表数据限制:
Excel 2003及以下的版本。一张表最大支持65536行数据,256列。
Excel 2007-2010版本。一张表最大支持1048576行,16384列。
也就是说你想几百万条轻轻松松一次性导入一张EXCEL表是不行的,你起码需要进行数据分割,保证数据不能超过104W一张表。
PHPexcel内存溢出:
既然数据限制在104W,那么数据分割就数据分割呗,于是你尝试50W一次导入表,然而PHPexcel内部有函数报内存溢出错误,然后你就不断的调小数据量,直到5W一次导入你都会发现有内存溢出错误。这是为什么呢,虽然你分割数据来导入多个数据表,但是最后PHPexcel内部还是一次性把所有表数据放进一个变量中来创建文件……额,这几百万数据一个变量存储,你想内存不溢出,还真有点困难。 
(后来看了一些文章发现PHPExcel也有解决方案,PHPExcel_Settings::setCacheStorageMethod方法更改缓冲方式来减小内存的使用)

csv坑

EXCEL这么麻烦,我不用还不行吗?我用csv文件储存,既不限制数量,还能直接用EXCEL来查看,又能以后把文件导入数据库,一举几得岂不是美哉?咦,少侠好想法!但是CSV也有坑哦!

输出buffer过多:
当你用PHP原生函数putcsv()其实就使用到了输出缓存buffer,如果你把几百万的数据一直用这个函数输出,会导致输出缓存太大而报错的,因此我们每隔一定量的时候,必须进行将输出缓存中的内容取出来,设置为等待输出状态。具体操作是:
ob_flush();
flush();
具体说明介绍:PHP flush()与ob_flush()的区别详解

EXCEL查看CSV文件数量限制:
大多数人看csv文件都是直接用EXCEL打开的。额,这不就是回到EXCEL坑中了吗?EXCEL有数据显示限制呀,你几百万数据只给你看104W而已。什么?你不管?那是他们打开方式不对而已?不好不好,我们解决也不难呀,我们也把数据分割一下就好了,再分开csv文件保存,反正你不分割数据变量也会内存溢出

代码

 $errno,
                "msg"  => $errstr,
                "file" => $errfile,
                "line" => $errline,
        ];
        print_r($data);
        echo "
"; exit; }); register_shutdown_function(function () { $error = error_get_last(); if ($error) { $errno = $error["type"]; $errfile = $error["file"]; $errline = $error["line"]; $errstr = $error["message"]; $data = [ "code" => $errno, "msg" => $errstr, "file" => $errfile, "line" => $errline, ]; print_r($data); exit; } }); $model = new Qushu(); echo date("H:i:s",time())."
"; $model->getDg(); echo date("H:i:s",time())."
"; class Qushu { public function getDg() { $header = [ 'id', 'uid', 'user_name', 'url', 'request_type', 'parameter', 'ip', 'type', 'time', 'agent' ]; $output = $this->csvSet("导出表名", $header); $data = $this->getCsv(); $i = 0; foreach ($data as $k => $v) { //输出csv内容 fputcsv($output, array_values($v)); $i++; } //关闭文件句柄 fclose($output) or die("can‘t close php://output"); } /** *获取csv内容 使用 yield */ public function getCsv() { $mysql = $dbh = new PDO('mysql:host=localhost;dbname=test', "root", "mysqlpw", [ PDO::ATTR_PERSISTENT => true ]);; $data = $mysql->query("select * from wclothing_log limit 1000000"); // 这里还可以继续优化,我们前面设置了最大的内存是1024M就是因为这里一下子从数据库读取了100W数据 // 我们还是可以分页获取 比如十万取一次 foreach ($data as $key => $val) { $a = [ $val['id'], $val['uid'], $val['user_name'], $val['url'], $val['request_type'], $val['parameter'], $val['ip'], $val['type'], $val['time'], $val['agent'] ]; yield $a;// 如果不了解生成器的同学、先去了解yield } } /**设置csv*/ public function csvSet($name, $head) { try { //设置内存占用 //为fputcsv()函数打开文件句柄 $output = fopen('php://output', 'w') or die("can‘t open php://output"); //告诉浏览器这个是一个csv文件 header("Content-Type: application/csv"); header("Content-Disposition: attachment; filename=$name.csv"); header('Cache-Control:must-revalidate,post-check=0,pre-check=0'); header('Expires:0'); header('Pragma:public'); // 文件名转码 $name = iconv('utf-8', 'gbk', $name); //输出表头 foreach ($head as $i => $v) { //CSV的Excel支持GBK编码,一定要转换,否则乱码 $head[$i] = iconv('utf-8', 'gbk', $v); } fputcsv($output, $head); return $output; } catch (Exception $e) { } } }

总结做法

分析完上面那些坑,那么我们的解决方案来了,假设数据量是几百万。
1、那么我们要从数据库中读取要进行数据量分批读取,以防变量内存溢出,
2、我们选择数据保存文件格式是csv文件,以方便导出之后的阅读、导入数据库等操作。
3、以防不方便excel读取csv文件,我们需要104W之前就得把数据分割进行多个csv文件保存
4、多个csv文件输出给用户下载是不友好的,我们还需要把多个csv文件进行压缩,最后提供给一个ZIP格式的压缩包给用户下载就好。

借鉴

你可能感兴趣的:(php-小记)