php批量导出百万级数据

本文实现php批量导出数据到csv,并打包成zip

注意: 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'];
}

完 ···

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

布尼卡

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值