设备
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格式的压缩包给用户下载就好。
借鉴