[原文来自于转载, 但他的结论不太正确, 尤其对foreach的判断这块上,  我拎过来进行修理 ]
 
在做数据统计时,难免会遇到大数组,而处理大数据经常会发生内存溢出,这篇文章中,我们聊聊如何处理大数组。
常见的大数组大多来自两种情况:
  • 大文件的处理
  • DB读取大数据的处理
这里重点讲下DB读取大数据的处理,顺便简单介绍下大文件处理,希望对大家有帮助,看完后可以轻松解决各种大数组问题。
 
大文件的处理
 
大家都知道,如果一个文件超过了memory_limit的设置,是不会被加载到内存中的,
试想下假如想要处理一个20G的文件,PHP需要怎么处理呢?
大文件的处理核心思路是:逐行读取。
这样基本可以做到无视文件大小,轻松处理大文件了。
 

DB读取大数据的处理
 
从数据库中读取大数据,我们先罗列一下可能会遇到的问题
  • 数据量太大无法从数据库中读取
  • 大数组无法处理
如果是数据量太大无法从数据库中读取,请优化数据库配置或者优化你的语句,
 
一般情况是建议优化SQL缩小查询范围,将数据分批进行处理,毕竟DB配置或机器硬件也不能无限优化的。
结论先行,DB读取大数据的处理核心思路是:变量用完及时销毁。
特别是我们循环处理大数组时,是很耗费内存的,所以如果能及时销毁用完的变量,就不用担心内存溢出了。
 
如何及时销毁变量呢?我们常用的做法可能是用完后销毁,如下
 
  $sql = "your sql";
  $rs = $DB->query($sql);
  $data = array();
  foreach($rs as $v){
     //your code
  }
  unset($rs);//销毁变量
 
示例中,用完 $rs 之后销毁确实可以释放内存,但实际上大数组的处理中,往往在循环内已经内存溢出无法执行到unset($rs)。那么我们自然而然就想到,能不能在循环内及时销毁用完的变量,是不是也可以及时释放内存呢?答案是可以的。
接下来给大家描述下,我在项目中遇到的问题和解决。
 
项目实践
 
项目背景
最近在开发的海外媒体绩效项目,其中有个计算模块,
需根据各个平台的投放数据(注册数、CPA、净收金额等指标)计算每个员工当月的绩效得分,分数用于辅助打绩效。
 
遇到问题
以5月份为例,从各个统计后台同步到绩效后台的数据量大约有70W+条记录,最极端的情况是,所有的数据都是同一个人投放的,
换句话说,计算这个员工的得分,我需要先从数据库读取这70W+条记录,然后在程序中进行逻辑计算。
在这里就遇到了刚才描述的问题,计算某员工得分,取出的数据量较小的时候,循环顺利执行完毕并且销毁了变量,但当取出的数据量较大的时候,在循环内就已经挂了,执行不到循环处理完毕之后销毁变量。
 
调试检验
自然而然我也想到了,能不能在foreach循环内,及时销毁用完的变量,从而释放内存呢?思路如下
 
  $sql = "your sql";
  $rs = $DB->query($sql);
  $data = array();
  foreach($rs as $k=>$v){
    //your code
    unset($rs[$k]);//销毁变量
  }
  unset($rs);//销毁变量
从结果上看是没有效果的,原来执行不了的仍然是执行不了,我加上些断点和打印信息来辅助排查为什么没有达到预期。
 
以下是我实际项目去做的测试,代码如下
<?php
set_time_limit(0);
ini_set('memory_limit', '1024M');//视自身业务情况,这里临时分配足够内存去测试 echo "\r\nstart:" . memory_get_usage();
$res = file_get_contents("./content.txt"); //文件里的是30W行整数
$rs = explode("\n", $res);         //这两段可当做是上面的大量的sql查询的结果 echo "\r\nbefore-data:" . memory_get_usage();
$num = count($rs);
$sum = 0;
foreach($rs as $k=>$value) {
//计算实现逻辑
$sum += $value;
if($k % 50000 == 0 ){
  echo "\r\ncount:".count($rs);
  echo "\r\nafterMemory:" . memory_get_usage();
}
unset($rs[$k]);
}
echo "\r\nfinnal-data:" . memory_get_usage();
大约跑了30W条测试数据,输出结果:
[root@07 server]# php mem.php
 
start:352496
before-data:15926768
count:201121
afterMemory: @1.
count:151121
afterMemory: @2
count:101121
afterMemory:24319552
count:51121
afterMemory:24319552
count:1121
afterMemory:24319552
 
finnal-data: 9490928
 
从结果上看,@1~@2间要做计算内存持续增长, 程序一般在这里会爆掉 [假如又还没unset语句], 但虽然在循环内$rs数组写明要逐步被UNSET了,但是内存却没有太大变化,没有被释放掉。反而在循环结束后才生效释放出来了, 说明在循环内的unset()必须等循环结束后才会统一回调递归释放掉内存.
 
但是换种情况循环的时候加引用的话,如下在$value前加个 "&" 符号再来测试发现就可以立即释放内存了,因为&符表引用
 
foreach($rs as $k=> &$value ) {
//计算实现逻辑
$sum += $value;
if($k % 50000 == 0 ){
echo "\r\ncount:".count($rs);
echo "\r\nafterMemory:" . memory_get_usage();
}
unset($rs[$k]);
}
[root@07 server]# php mem.php
 
start:352496
before-data:15926768
count:201121
afterMemory:15926816
count:151121
afterMemory:14326816
count:101121
afterMemory:12726816
count:51121
afterMemory:11126816
count:1121
afterMemory:9526816
finnal-data:9490976
 
得到的结果完全不同, 在循环内加引用会立即释放内存.
 

当然也可以使用for循环来得到类似的结果,使用for循环,里边调用unset()的话会立即释放内存.
for ($k=0; $k < $num; $k++) {
$value = $rs[$k];
//计算实现逻辑
$sum += $value;
if($k % 50000 == 0 ){
echo "\r\ncount:".count($rs);
echo "\r\nafterMemory:" . memory_get_usage();
}
unset($rs[$k]);
}
[root@07 server]# php mem.php
 
start:352560
before-data:15926832
count:201121
afterMemory:15926832
count:151121
afterMemory:14326832
count:101121
afterMemory:12726832
count:51121
afterMemory:11126832
count:1121
afterMemory:9526832
finnal-data:9490992
 
从结果可以看出,随着循环的进行,$rs数组逐步被UNSET并且释放了内存,这里涉及到PHP的垃圾回收机制,有兴趣的朋友可以继续深入研究。
 
至此,DB读取大数据的问题处理完毕。
 
补充几点小建议
  • file_get_contents是一次性把文件内容缓存到内存,相比fgets逐行读取效率要高些,但受限于内存等原因处理大文件时选择逐行读取更合理。
  • foreach循环效率高于for循环,譬如for循环每次循环都要判断$i是否小于count,就耗费了一些时间,所以能用foreach就用foreach循环。
  • for循环在外部做count比在条件中做count效率更高些,减少了每次循环调用count函数,并且由于处理大数据时会使用unset,导致count($rs)值一直变动,所以for循环在外部做count更合适。
  • 为了更好的用户体验,这种大数组处理尽量是定时任务或后台处理
结论: 不管是大文件处理,还是DB读取大数据处理,其实都是用时间换空间,哪种方式更适合,在实际生产中需要依据自身业务的特点去设计。
 
 

最新文章

  1. 让浏览器不再显示 https 页面中的 http 请求警报
  2. rem的使用
  3. C语言获得文件一行
  4. sqoop的export导入到oracle中
  5. Winform 中的KeyDown
  6. Sharepoint 2010 RBS 的安装和配置
  7. float,double和decimal的精度问题
  8. DataGridView实现倒计时功能(源码)
  9. NOIP(提高组)DAY1国王游戏
  10. OGG 文档
  11. Win10下C:\Users\John以账户名称命名的系统文件夹用户名的修改
  12. 对象作为 handleEvent
  13. 我的Java设计模式-建造者模式
  14. 12.1、Libgdx的图像之持续性和非持续性渲染
  15. 爬虫框架Scrapy 之(一) --- scrapy初识
  16. Error: EACCES: permission denied, mkdir &#39;......node-sass/build&#39;错误解决方案
  17. react-native Execution failed for task &#39;:app:prepareRnReduxReactNativeUpdateUnspecifiedLibrary&#39;报错
  18. MySQL外键约束_ON DELETE CASCADE/ON UPDATE CASCADE
  19. Alpha冲刺(5/10)——追光的人
  20. Vue.js双向绑定原理

热门文章

  1. 【学习笔记】HTML基础:使用html制作网页
  2. struts2 :Unable to load configuration. ……struts-default.xml:46:178异常解决
  3. 位运算(4)——Missing Number
  4. PAT 1059. Prime Factors
  5. html页面的加载顺序
  6. gsap
  7. MicroService
  8. mongodb 3.4 学习 (五)备份&amp;恢复
  9. I want to be a Great Web Front-end Developer
  10. msysGit删除github文件