新闻资讯  快讯  焦点  财经  政策  社会
互 联 网   电商  金融  数据  计算  技巧
生活百科  科技  职场  健康  法律  汽车
手机百科  知识  软件  修理  测评  微信
软件技术  应用  系统  图像  视频  经验
硬件技术  知识  技术  测评  选购  维修
网络技术  硬件  软件  设置  安全  技术
程序开发  语言  移动  数据  开源  百科
安全防护  资讯  黑客  木马  病毒  移动
站长技术  搜索  SEO  推广  媒体  移动
财经百科  股票  知识  理财  财务  金融
教育考试  育儿  小学  高考  考研  留学
您当前的位置:首页 > IT百科 > 程序开发 > 语言 > php

PHP导出百万条数据方法

时间:2019-09-11 10:43:30  来源:  作者:

公司目前有一个需求,需要对一个日增量在20万+数据量的数据表中的数据进行可自定义条件筛选的导出数据,该功能需要对多个部门进行开发使用,要保证功能可用的前提下,尽量优化体验。

首先介绍一下当前可利用的资源:

1、MySQL - 一主库双从库。

2、分布式服务器集群,选择其中一台中型机作为脚本执行载体。

3、文件系统 - 可以支持上传大数据量文件。

4、编程语言php

技术难点:

1、数据太大,对服务器配置要求较高,导出过程中涉及数据的处理(例如各种ID转换名称等操作,我们这次需求这种太多了~~非常的坑)对内存消耗很大,其次涉及到文件压缩,因此对CPU要求较高。

2、因为是跨系统部署,如果走接口,数据量随随便便上百M,传输速度太慢(项目是对外网开放的,然后数据只允许内网访问),那么该如何解决?

3、数据安全性较高,需要对所有导出进行记录,那么如何保证数据安全?

| 技术方案

第一步:设计数据库,对所有导出任务进行实时记录,也可以采用redis,为了方便数据的持久化,我最终采用了mysql数据库的方案。表结构具体包括:ID、用户ID、用户名、发起请求时间、导出具体的参数(包括各个维度的参数选择等,具体根据自身业务而定),任务是否正在处理标识(防止任务多次被处理),导出是否成功标识(可以与前一个用一个字段区分),删除标识等(假删除,便于记录用户实际操作日志)。

第二步:前台界面编写,具体包括参数选择、导出记录列表等,作用:触发导出任务创建,记录于导出表中,状态:待处理。

第三步:编写导出脚本对任务进行监控并处理,如果有导出任务自动对其执行导出操作。

这里有一个小问题:为什么不在前台触发任务的时候直接执行导出,而是有单独的脚本来执行导出呢?这就是现实业务导致的,因为我们对外开放的机器中有一些是配置很低的,为了保证导出的成功率,我们需要一台配置较高的机器来独立执行导出任务。

| 导出流程

具体流程参考下图

PHP导出百万条数据方法

 

| 代码实现

这里主要着重介绍一下导出脚本的代码,其他步骤的代码根据自己的业务自行编写就可以了。

注意:因为数据量过大~一次性导出可想而知是不合理的,所以我使用了分页导出的形式~

首先查询数据总条数、然后通过每页导出的条数来计算具体导出的页数~

# 获取数据总条数
$dataCount = Data_ExportModel::getExportZipTotalCount($params);
$dataCount = $dataCount[0]['count_num'];
# csv
# 输出Excel文件头,可把user.csv换成你要的文件名
$mark = '/tmp/export';
$stepLen = 20000;//每次只从数据库取100000条以防变量缓存太大
# 每隔$limit行,刷新一下输出buffer,不要太大,也不要太小
$limit = 20000;
$maxFileCount = 1000000;
# buffer计数器
$cnt = 0;
$head = self::initColumnDataV2(); // 表头部分根据自身业务自行调整
$fileNameArr = array();
$salesStatisticsData = array();
$startLimitId = 0;

首次导出的每页条数我定的10万条,后来发现对内存消耗过大,改成了两万条,这样的导出速度会慢一点,建议五万条比较适中一点。

for ($j = 0; $j < ceil($dataCount / $maxFileCount); $j++) {
 $startSelect = ceil($maxFileCount / $stepLen)*$j;
 $fileCsvName = $mark . '_'.$j*$maxFileCount.'_' . ($j+1)*$maxFileCount . '.csv';
 $fp = fopen($fileCsvName, 'w'); //生成临时文件
 $fileNameArr[] = $fileCsvName;
 # 将数据通过fputcsv写到文件句柄
 fputcsv($fp, $head);
 for ($i = 0; $i < 50; $i++) { // 单个文件支持100万数据条数
 $startNum = $j*$maxFileCount + $i*$limit;
 if ($startNum > $dataCount) {
 break; // 跳出循环
 }
 # 查询数据
 $dataSource = Data_ExportModel::getExportZipTotalInfo($params, $startNum, $stepLen, $startLimitId);
 $endMicroTime = microtime(true);
 printf("\n[%s -> %s] Begin Time : %s, End Time : %s, Total Count : %s, CostTime: %s.\n", __CLASS__, __FUNCTION__, $params['begin_date'], $params['end_date'], count($dataSource), ($endMicroTime - $startMicroTime));
 if (empty($dataSource)) {
 continue;
 }
 $endMicroTime = microtime(true);
 foreach ($dataSource as $_key => $_data) {
 $cnt++;
 if ($limit == $cnt) {
 # 刷新一下输出buffer,防止由于数据过多造成问题
 ob_flush();
 flush();
 $cnt = 0;
 }
 # 数据处理部分,根据自身业务自行定义,注意中文转码
 $salesStatisticsData['name'] = iconv('utf-8', 'GB18030', $salesStatisticsData['c_name']);
 fputcsv($fp, $salesStatisticsData);
 }
 }
 fclose($fp); # 每生成一个文件关闭
}
# 进行多文件压缩
$zip = new ZipArchive();
$number = rand(1000,9999);
$filename = $mark."_".$params['begin_date']."_".$params['end_date'] ."_".$number. ".zip";
$zip->open($filename, ZipArchive::CREATE); //打开压缩包
foreach ($fileNameArr as $file) {
 $zip->addFile($file, basename($file)); //向压缩包中添加文件
}
$zip->close(); //关闭压缩包
if (!file_exists($filename)) {
 // 首次执行检查生成的压缩文件是否存在失败,进行二次尝试。。。
 $endMicroTime = microtime(true);
 # 进行二次多文件压缩
 $number = rand(1000,9999);
 $filename = $mark."_".$params['begin_date']."_".$params['end_date'] ."_".$number. ".zip";
 if (file_exists($filename)) {
 unlink($filename);
 }
 $zip->open($filename, ZipArchive::CREATE); //打开压缩包
 foreach ($fileNameArr as $file) {
 $zip->addFile($file, basename($file)); //向压缩包中添加文件
 }
 $zip->close(); //关闭压缩包
}
if (file_exists($filename)) {
 $content = file_get_contents($filename);
 // 解决读取文件偶尔出现失败的问题,第一读出为空则尝试第二次读取
 $forNum = 0;
 while (!$content) {
 $forNum++;
 @$content = file_get_contents($filename);
 if ($forNum > 10) {
 break; // 防止出现异常情况导致死循环,最多重试10次
 }
 }
} else {
 $endMicroTime = microtime(true);
 # 删除临时文件,防止占用空间
 foreach ($fileNameArr as $file) {
 if (is_file($file)) {
 unlink($file);
 }
 }
 // 记录错误日志并且报警
 return false;
}
# 删除临时文件,防止占用空间
foreach ($fileNameArr as $file) {
 if (is_file($file)) {
 unlink($file);
 }
}

最后将生成好的文件存入文件系统,上传成功之后反转导出状态,前台检测到导出成功自动进行下载即可。



Tags:PHP 导出数据   点击:()  评论:()
声明:本站部分内容来自互联网,内容观点仅代表作者本人,如有任何版权侵犯请与我们联系,我们将立即删除。
▌相关评论
发表评论 共有条评论
用户名: 密码:
验证码: 匿名发表
▌相关推荐
公司目前有一个需求,需要对一个日增量在20万+数据量的数据表中的数据进行可自定义条件筛选的导出数据,该功能需要对多个部门进行开发使用,要保证功能可用的前提下,尽量优化体验...【详细内容】
2019-09-11   PHP 导出数据  点击:(0)  评论:(0)  加入收藏
推荐资讯
相关文章
栏目更新
栏目热门