PHP百万级数据导出解决方案
背景:
公司需要将一批海量数据导出给用户,数据来源于数据库,且数据条数在百万级以上未来可能更多。
分析:
1、使用EXCEL格式导出
如果使用excel文件导出目前PHP支持的库有 phpoffice/phpspreadsheet 目前也是start比较多的,让我来分析一下他的优缺点。
优点:使用简单,容易上手
缺点:很消耗内存,导出的数据量越大消耗的内存也越大当然导出的时间也越大,而且excel行数是有限制的
2、使用CSV格式导出
相比前者csv格式在导出大数据方面还是有决定的优势,这里我们使用的是laravel的组件库league/csv
优点:在大数据导出方面消耗内存小
缺点:数据不是特别的直观
解决方案:
由于这个业务场景下的数据无法预估到底会产生多少数据,可能百万,可能千万那么我采用了csv的方案。但是csv的方案也会存在 时间问题,如果数据越大客户端等待的时间也就越大。当然这个无论是方案excel方法还是csv都会遇到这个问题。如果是平常的http协议肯定是会连接响应超时的,这里我采用的websocket方法传输,使用websocket方法是比较合适的同时也可以实时获取导出状态。
/**
* 这里用的是hyperf框架但是这个ORM还是用的laravel的底层
**/
//从数据库读取对应的数据
public function getData($i, $limit)
{
for ($n = 1; $n <= $i; ++$n) {
$logs = Db::table('call_log')->select(['call_no', 'called_no', 'ring', 'agent', 'call_time_length'])->paginate($limit, ['call_no', 'call_time_length'], '', $n)->items();
$logs = json_decode(json_encode($logs), true); //将结果集转为数组类型
yield $logs; //借助生成器完成分页查询
}
}
//这个为主方法
public function index()
{
$starttime = explode(' ', microtime()); //记录程序执行前的时间
$a = memory_get_usage(); //记录程序执行前所用的内存
$count = $this->dataCount(); //先查出有多少条数据
echo PHP_EOL . '总数据条数:' . $count . PHP_EOL;
$count = (int)ceil($count / 100000); //设置limit大小类似分页查询 limit越小等待时间越长,消耗的内存越小 100000表示每次查询100000条数据写入
$head = ['主叫', '被叫', '响铃时间', '坐席', '通话时长(秒)'];
$csv = Writer::createFromPath("public/".time().".csv", 'w+'); //写入文件
$res = $this->getData($count, 100000);
$csv->insertOne($head);
foreach ($res as $values) {
$csv->insertAll($values);
$b = memory_get_usage();
echo '总消耗内存' . sprintf('%.3f', ($b - $a) / (1024 * 1024)) . 'M' . PHP_EOL;
}
$endtime = explode(' ', microtime());
$thistime = $endtime[0] + $endtime[1] - ($starttime[0] + $starttime[1]);
$thistime = round($thistime, 3);
echo "本网页执行耗时:" . $thistime . " 秒。" . PHP_EOL;
return 1;
}
【程序运行图】
导出一百万条数据用了两分钟左右的时间,但是内存消耗只消耗了77M当然你可以降低分页条数来进一步降低内存的消耗。实验中我用的是10万数据,你可以更低但是所消耗的时间也会越长,这个就像软件工程里讲的一样为了提升效率要么牺牲空间提高时间,要么牺牲时间降低空间。