PHP大数据量导出优化方案:20万+日增量数据表的自定义筛选与导出技巧

链接放在公众号里是较为妥当的。因为放在公众号里,若过期了还可以进行修改。这便是不将其放在文章里的缘由所在。倘若有需要,大家可以关注“PHP 自学中心”,然后回复对应的关键词,即可获取链接与提取码。感谢大家一直以来的支持与信任。

PHP大数据量导出优化方案:20万+日增量数据表的自定义筛选与导出技巧

使用正则动态修改配置文件
链接:https://pan.baidu.com/s/155RKUawCqdZsUmLOA46JNw 

SESSION示例之购物车
链接:https://pan
.baidu.com/s/1_d6-UqKxmGCR2azhi836Qw 

密码:本公众号回复 21112018 即可领取

文章正文

公司当前有一个需求,即要对一个日增量为 20 万+的数据量的数据表中的数据进行可按自定义条件筛选后导出。此功能需对多个部门进行开发以供使用,在保证功能可用的同时,要尽量将体验进行优化。

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

1、MySql - 一主库双从库。

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

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

4、编程语言PHP。

技术难点:

数据较大,这使得对服务器配置有较高要求。在导出过程中,会涉及数据的处理,像各种 ID 转换为名称等操作,而此次需求中这类操作很多,非常麻烦。这些数据处理对内存消耗很大。此外,还涉及文件压缩,所以对 CPU 要求也较高。

因为是跨系统进行部署,若采用走接口的方式,数据量往往会轻易达到上百 M,这样会导致传输速度非常慢。而该项目是对外网开放的,同时数据又只允许内网访问,那么在这种情况下,应该如何去解决呢?

数据安全性较高,并且需要对所有导出进行记录。那么,怎样才能保证数据的安全呢?

| 技术方案

设计数据库,对所有导出任务进行实时记录。可以采用 redis 来实现这一功能。为了方便数据的持久化,最终选择了 mysql 数据库的方案。删除标识等,此为假删除,便于记录用户的实际操作日志。

第二步是进行前台界面编写,其中包含参数选择以及导出记录列表等内容。其作用是触发导出任务的创建,并且会将相关记录记录于导出表中,目前的状态是待处理。

第三步,编写导出脚本,对任务进行监控并处理。若存在导出任务,便自动对其执行导出操作。

这里存在一个小问题:为何不在前台触发任务时直接进行导出,却要用单独的脚本来执行导出呢?这是由现实业务所导致的。因为我们对外开放的机器里,有一些机器的配置是很低的。为了确保导出的成功率,我们需要一台配置较高的机器来单独执行导出任务。

| 导出流程

具体流程参考下图

| 代码实现

这里主要介绍导出脚本的代码,其他步骤的代码需根据自身业务自行编写。

由于数据量非常大,一次性将所有数据导出这种做法是不合理的,因此我采用了分页导出的方式。

先查询数据的总条数,接着依据每页导出的条数去计算具体要导出的页数。

# 获取数据总条数
Data_ExportModel 获取了导出压缩包的总数,总数为 $dataCount,获取的依据是传入的参数 $params。
$dataCount = $dataCount[0]['count_num'];
# csv
输出 Excel 文件的头,能够将 user.csv 替换为你所需要的文件名。
$mark = '/tmp/export';
$stepLen = 20000;//每次只从数据库取100000条以防变量缓存太大
每隔$limit行就刷新一下输出buffer,这个buffer既不要太大,也不要太小。
$limit = 20000;
$maxFileCount = 1000000;
# buffer计数器
$cnt = 0;
$head = self::initColumnDataV2(); // 表头部分根据自身业务自行调整
$fileNameArr = array();
$salesStatisticsData = array();
$startLimitId = 0;

首次导出时,我设定每页的条数为 10 万条。之后发现这对内存消耗过大,于是将其改成了 2 万条。这样一来,导出速度会变慢一些。我建议每页的条数为 5 万条,这样比较适中。

for ($j = 0; $j < ceil($dataCount / $maxFileCount); $j++) {
$startSelect 等于 $maxFileCount 除以 $stepLen 的向上取整结果再乘以 $j 。
    $fileCsvName = $mark . '_'.$j*$maxFileCount.'_' . ($j+1)*$maxFileCount . '.csv';
$fp 被用于打开 $fileCsvName 这个文件。'w'); //生成临时文件
$fileNameArr 包含了 $fileCsvName ;$fileNameArr 被赋值为 $fileCsvName ;$fileCsvName 被添加到 $fileNameArr 中 。
    # 将数据通过fputcsv写到文件句柄
    fputcsv($fp, $head);

    for ($i = 0; $i < 50; $i++) { // 单个文件支持100万数据条数
$startNum 等于 $j 乘以 $maxFileCount 再加上 $i 乘以 $limit ;$startNum 是通过这种方式计算得出的 ;$startNum 的值由 $j 、$maxFileCount 、$i 和 $limit 共同决定 。
        if ($startNum > $dataCount) {
            break;  // 跳出循环
        }
        # 查询数据
Data_ExportModel 用于获取导出压缩包的总信息,其获取的方式为调用 getExportZipTotalInfo 方法,并传入参数 $params、$startNum、$stepLen 和 $startLimitId,获取到的结果赋值给 $dataSource。

$endMicroTime 等于 microtime 函数的返回值。true);
        printf([%s 转换为%s]开始时间:%s,结束时间:%s,总计数:%s,耗时:%s。, __CLASS__, __FUNCTION__, $params['begin_date'], $params['end_date'使用 count 函数获取 $dataSource 的数量,然后计算 ($endMicroTime - $startMicroTime) 的值。

        if (empty($dataSource)) {
            continue;
        }

    $endMicroTime 等于 microtime 函数的返回值。true);
        foreach对于 ($dataSource 作为 $_key 并映射到 $_data) {
            $cnt++;
            if ($limit == $cnt) {
                # 刷新一下输出buffer,防止由于数据过多造成问题
                ob_flush();
                flush();
                $cnt = 0;
            }
            # 数据处理部分,根据自身业务自行定义,注意中文转码

销售统计数据中,各个时间段的销售数据各不相同。有的时间段销售数据较高,有的时间段销售数据较低。不同时间段的销售数据差异明显,这反映了市场需求的变化情况。销售数据的高低起伏与市场的动态紧密相关,通过对销售统计数据的分析,可以更好地了解市场的需求趋势,为企业的决策提供依据。'name'] = iconv('utf-8', 'GB18030', $salesStatisticsData['c_name']);

使用 fputcsv 函数将 $salesStatisticsData 写入到 $fp 中。
        }
    }
    fclose($fp);  # 每生成一个文件关闭
}

# 进行多文件压缩
$zip = new ZipArchive();
$number = rand(1000,9999);
$filename = $mark."_".$params['begin_date']."_".$params['end_date'] ."_".$number. ".zip";
打开名为 $filename 的文件,并创建一个 ZipArchive 对象,然后将其赋值给 $zip 变量,执行 $zip->open 操作。//打开压缩包
foreach ($fileNameArr as $file) {
$zip 把文件 $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);
    }
使用 ZipArchive 类的 create 模式打开名为 $filename 的文件,并将其存储在 $zip 变量中。//打开压缩包
    foreach ($fileNameArr as $file) {
$zip 向其中添加了一个文件,这个文件是 $file,添加后的文件名是 basename($file) 。//向压缩包中添加文件
    }
    $zip->close();  //关闭压缩包
}

if (file_exists($filename)) {
获取文件内容的操作是通过 file_get_contents 函数来实现的,将其赋值给变量 $content,其中的参数是 $filename 。
    解决读取文件偶尔出现失败的情况,首先判断第一次读出是否为空,如果为空则尝试进行第二次读取。
    $forNum = 0;
    while (!$content) {
        $forNum++;
使用 file_get_contents 函数获取了 $filename 对应的内容,并将其赋值给了 @$content 。
        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);
    }
}

最后把生成好的文件存到文件系统里,在上传成功之后把导出状态反转,前台一旦检测到导出成功,就会自动进行下载。

以上是文章的全部内容。若有学习与经验交流需求,可添加微信成为好友。一同学习,进行交流并取得进步!请备注:码农

你可能想看:
« 上一篇
下一篇 »

相关推荐

php实时推送,php实地培训

2026年10月05日

53阅读

php乱码出现问号的原因,php乱码出现问号的原因有哪些

2026年10月05日

45阅读

php一句话木马,一句话木马ctf

2026年10月05日

48阅读

php获取数组长度,php中如何获取数组的长度

2026年10月05日

50阅读

php面试题库及答案,php程序员面试真题库

2026年10月05日

47阅读

phpcontent-type修改,php源码二次修改需要多少钱

2026年10月05日

42阅读