注意: PHP需要安装zip扩展
都知道普通的phpExcel导出量大的数据会占用很大的内存空间,甚至服务器会扛不住导致内存溢出,网站崩溃。此时我们可以将部分用到大量导出的地方换为导出批量导出csv然后打包成zip提供下载,因csv读写性能比excel高很多,再配合分批导出打包下载,效率和体验上会比phpExcel好。
少废话,上代码
准备测试数据:
数据表
CREATE TABLE `test_user` (
`id` bigint(11) NOT NULL AUTO_INCREMENT,
`name` varchar(20) NOT NULL COMMENT '名字',
`age` tinyint(2) NOT NULL,
`hobby` varchar(20) NOT NULL COMMENT '爱好',
`create_time` datetime DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1183001 DEFAULT CHARSET=utf8 COMMENT='测试表';
插入百万测试数据
ini_set ('memory_limit', '256M');
set_time_limit(0);
$user = db('test_user');
$time = microtime(true);
$create_time = date('Y-m-d H:i:s');
$num = 5000;//一次插入5000条
$data = [];
for ($i=0; $i < $num; $i++) {
$data[] = [
'name' => '小爱同学',
'age' => 18,
'hobby' => '摄影',
'create_time' => $create_time,
];
}
for ($i=0; $i < 200; $i++) {
$user->insertAll($data);
}
echo microtime(true)-$time;
方法:
/**
* 导出csv并压缩成zip
* @param array $header 表头
* @param object $model 模型对象
* @param string $file 导出地址
* @param string $name 导出文件名,不支持中文
* @param int $num 每次查询条数/每个csv文件的记录条数,默认1w
* @return array [ 'status' => true,'data' => 'export/file/downcsv20190121_106.zip' ]
* @author smt date 2019-1-21
*/
public function putCsv($header, $model,$file = 'export/file', $name = 'down_csv',$num = 20000)
{
set_time_limit(0);//设置不限制脚本执行时间
ini_set('memory_limit', '128M');//设置脚本所能够申请到的最大内存
// 参数校验
if (!is_array($header) || empty($header)) return [ 'status'=>false , 'data' => '表头参数不正确' ];
if (!is_object($model)) return [ 'status'=>false , 'data' => '数据对象错误' ];
try {
$sqlCount = $model->count();
if ($sqlCount < 1) return [ 'status'=>false , 'data' => '数据总数为0' ];
} catch (Exception $e) {
return [ 'status'=>false , 'data' => $e->getMessage() ];
}
$pattern = '/[^\x00-\x80]/';
if ( preg_match($pattern,$file) ) return [ 'status'=>false , 'data' => '导出地址不能包含中文' ];
if ( preg_match($pattern,$name) ) return [ 'status'=>false , 'data' => '导出文件名不能包含中文' ];
if ( $num > 20000 ) return [ 'status'=>false , 'data' => '查询条数最多每页2万条' ];
if ( !$this->mkdirMore($file) ) return [ 'status'=>false , 'data' => '创建目录失败' ];
// 处理表头编码
$header = implode(",",$header);
$header = iconv('UTF-8', 'GBK//IGNORE', $header);
$header = explode(",", $header);
$sqlLimit = $num;//每次只从数据库取 $num 条以防变量缓存太大
$limit = $num;// 每隔$limit行,刷新一下输出buffer,不要太大,也不要太小
$cnt = 0;// buffer计数器
$fileNameArr = array();//文件名集合
$file_name = $file.'/'.$name.date('YmdHis').mt_rand(10000,99999);//组成路径及文件名
// 逐行取出数据,不浪费内存
for ($i = 0; $i < ceil($sqlCount / $sqlLimit); $i++) {
//生成临时文件
$inum = $i+1;
$fp = fopen($file_name . '_' . $inum . '.csv', 'w');
$fileNameArr[] = $file_name . '_' . $inum . '.csv';
// 将行格式化为 CSV 并写入一个打开的文件
fputcsv($fp, $header);
// 分批查询数据
$dataArr = $model->limit($i * $sqlLimit,$sqlLimit)->select();
foreach ($dataArr as $row) {
$cnt++;
if ($limit == $cnt) {
//刷新一下输出buffer,防止由于数据过多造成问题
ob_flush();
flush();
$cnt = 0;
}
$str = implode("@@@@",$row);
$str = iconv('UTF-8', 'GBK//IGNORE', $str);
$str = str_replace(",","|",$str);
$row = explode("@@@@", $str);
fputcsv($fp, $row);
}
//关闭文件
fclose($fp);
}
//进行多个文件压缩
$zip = new \ZipArchive();
$filename = $file_name . ".zip";
$zip->open($filename, \ZIPARCHIVE::CREATE); //打开压缩包
foreach ($fileNameArr as $file) {
$zip->addFile($file, basename($file)); //向压缩包中添加文件
}
$zip->close(); //关闭压缩包
foreach ($fileNameArr as $file) {
unlink($file); //删除csv临时文件
}
return [ 'status'=>true , 'data' => $filename];
}
// 递归创建目录
public function mkdirMore($path, $mode = 0777){
if(is_dir($path)){
return true;
}else{
if(mkdir($path, $mode, true)) {
return true;
}else{
return false;
}
}
}
测试:
// 这里查询20万条数据测试
$model = db('test_user')->where('id','<=',200000);
$header = ['用户ID','姓名','年龄','爱好','注册时间'];// 表头
$file = 'export/file';// 导出地址
$name = 'downcsv';// 导出文件名,结果为:downcsv2019012116453395196.zip(文件名+年月日时分秒+5个随机数)
$res = $this->putCsv( $header,$model,$file,$name,20000 );
if ($res['status']) {
echo '成功:';
echo $res['data'];
}else{
echo '失败:';
echo $res['data'];
}
完 ···