导出百万级数据

记录一下(初步实现)

<?php
//让程序一直运行  
set_time_limit(0);
//设置程序运行内存  
ini_set('memory_limit', '128M');
$fileName = '商品导出数据';
header('Content-Encoding: UTF-8');
header("Content-type:application/vnd.ms-excel;charset=UTF-8");
header('Content-Disposition: attachment;filename="' . $fileName . '.csv"');
//打开php标准输出流 
$fp = fopen('php:output', 'a');
//添加BOM头,以UTF8编码导出CSV文件,如果文件头未添加BOM头,打开会出现乱码。  
fwrite($fp, chr(0xEF) . chr(0xBB) . chr(0xBF));
//添加导出标题
  fputcsv($fp, ['商品', '价格', '库存']);  
//链接数据库
$dsn = "mysql:host=127.0.0.1;port=3306;dbname=db_www;charset=utf8";
$pdo = new PDO($dsn, 'root', '');
$step = 100;
//循环次数 
$nums = 10000;
//每次导出数量 
for ($i = 0; $i < $step; $i++) {
    $start = $i * 10000;
    $sql = "SELECT product_code,price,qty FROM `products` ORDER BY `id` LIMIT {$start},{$nums}";
    $productMysql = $pdo->query($sql);
    $result = $productMysql->fetchAll(PDO::FETCH_ASSOC);
    foreach ($result as $item) {
        fputcsv($fp, $item);
    }
    //每1万条数据就刷新缓冲区  
 ob_flush();
    flush();
}

根据大佬们的建议修整后为

<?php
//让程序一直运行
set_time_limit(0);
//设置程序运行内存
ini_set('memory_limit', '128M');
$fileName = '商品导出数据';
header('Content-Encoding: UTF-8');
header("Content-type:application/vnd.ms-excel;charset=UTF-8");
header('Content-Disposition: attachment;filename="' . $fileName . '.csv"');
//打开php标准输出流
$fp = fopen('php:output', 'a');
//添加BOM头,以UTF8编码导出CSV文件,如果文件头未添加BOM头,打开会出现乱码。
fwrite($fp, chr(0xEF) . chr(0xBB) . chr(0xBF));
// 添加导出标题
fputcsv($fp, ['商品', '价格', '库存']);

// 链接数据库
$dsn  = "mysql:host=127.0.0.1;port=3306;dbname=db_www;charset=utf8";
$pdo  = new PDO($dsn, 'root', '');

$nums   = 10000; // 每次查询的数量
$lastId = 0; // 上一批数据的最后一个ID
do {
    $sql = "SELECT product_code, price, qty FROM `products` WHERE id > :lastId ORDER BY `id` LIMIT :nums";
    $stmt = $pdo->prepare($sql);
    $stmt->bindParam(':lastId', $lastId, PDO::PARAM_INT);
    $stmt->bindParam(':nums', $nums, PDO::PARAM_INT);
    $stmt->execute();
    $result = $stmt->fetchAll(PDO::FETCH_ASSOC);
    foreach ($result as $item) {
        fputcsv($fp, $item);
    }
    $lastId = $result[count($result) - 1]['id'];
    ob_flush();
    flush();
} while (count($result) > 0);

fclose($fp);
本作品采用《CC 协议》,转载必须注明作者和本文链接
《L02 从零构建论坛系统》
以构建论坛项目 LaraBBS 为线索,展开对 Laravel 框架的全面学习。应用程序架构思路贴近 Laravel 框架的设计哲学。
《G01 Go 实战入门》
从零开始带你一步步开发一个 Go 博客项目,让你在最短的时间内学会使用 Go 进行编码。项目结构很大程度上参考了 Laravel。
讨论数量: 8

可否再优化一下,百万数据,使用chunk分块,例如分为10块,并且使用定时任务,这样效率会不会更高一些?for循环里使用查询,这样的效率肯定是不高的。

$total = 1000000;
$times = 10;
YouModel::chunk($total/$$times, function (Collection $youmodels) {
    foreach ($youmodels as $model) {
        // ...
    }
});
5个月前 评论

LIMIT {$start},{$nums} 这个用法到后面会越来越慢,可以换成记录当前循环的最大ID,每次取大于该ID的10000条数据。

5个月前 评论
辰曦 (楼主) 5个月前

xlswriter可以试试

5个月前 评论

如果不要求实时性的话直接定时任务异步导出

5个月前 评论

用php导出复杂业务百万数据级要几十秒, 换成go的协程4秒 :smile:

5个月前 评论
misyuan 5个月前
郭扑克 5个月前

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!
未填写
文章
1
粉丝
1
喜欢
1
收藏
2
排名:2202
访问:731
私信
所有博文
社区赞助商