导出百万级数据

| 技术方案

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

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

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

| 代码实现

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

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

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

# 获取数据总条数
$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);
    }
}

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

暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇