百万级数据导出

数据导出的瓶颈在于数据查询,数据的条数查询大量消耗内存,导致服务宕机,所以按照主键id来分批查询数据

/**
     * 百万级数据导出
     */
    public function excelout(){
        //不限制执行时间,以防超时
        set_time_limit(0);
        //文件名
        $xlsName = iconv('utf-8', 'gb2312', 'user');//文件名称
        //统计总行数
        $sqlCount = 0;
        //表头
        $xlsCell = ['id','手机号','时间'];
        //对应表头的字段
        $fields = 'id,phone,create_time';

        //查询数据库中最大的id号,然后根据号分批导出
        $sqlCount = db('webuser')->max('id');

        //每次取多少条
        $sqlLimit = 50000;//每次只从数据库取2000条


        // buffer计数器
        $cnt = 0;
        $fileNameArr = array();

        //分段执行,以免内存写满
        for ($i = 0; $i < ceil($sqlCount / $sqlLimit); $i++) {
            $pathFilename = ROOT_PATH.DS.'public'.DS.DS.'excel'.DS.$xlsName;
            $fp = fopen($pathFilename . '_' . $i . '.csv', 'w'); //生成临时文件
            $fileNameArr[] = $pathFilename . '_' .  $i . '.csv';//将临时文件保存起来

//            第一次执行时将表头写入
            if($i == 0){
                fputcsv($fp, $xlsCell);
            }
            $startId = $i * $sqlLimit;
            $endId = $startId + $sqlLimit;

            //查询出数据
            $xlsData = db('webuser')->field($fields)
                ->where('phone','<>', '')
                ->where('id',['<',$endId],['>=',$startId],'and')
//                ->fetchSql()
                ->select();
//            dump($xlsData);


            foreach ($xlsData as $k=>$v) {
                $v['create_time'] = date('Y-m-d H:i:s',$v['create_time']);
                $cnt++;
                //执行下一次循环之前清空缓冲区
                if ($sqlLimit == $cnt) {
                    ob_flush();
                    $cnt = 0;
                }
                //每行写入到临时文件
                fputcsv($fp, $v);
            }


            fclose($fp);  //每生成一个文件关闭
        }






        //将所有临时文件合并成一个
        foreach ($fileNameArr as $file){
            //如果是文件,提出文件内容,写入目标文件
            if(is_file($file) && filesize($file) != 0 ){
                $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'); //删除合并的临时文件
    }

 

posted @ 2021-01-30 09:38  Abner3721  阅读(116)  评论(0编辑  收藏  举报