[原文来自于转载, 但他的结论不太正确, 尤其对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读取大数据处理,其实都是用时间换空间,哪种方式更适合,在实际生产中需要依据自身业务的特点去设计。
 
 

PHP大数组,大文件的处理的更多相关文章

  1. C#+无unsafe的非托管大数组(large unmanaged array in c# without 'unsafe' keyword)

    C#+无unsafe的非托管大数组(large unmanaged array in c# without 'unsafe' keyword) +BIT祝威+悄悄在此留下版了个权的信息说: C#申请一 ...

  2. Mina传输大数组,多路解码,粘包问题的处理

    我的实际情况: 1,传递的业务数据种类很多,这就决定了我们要用多路解码器,MINA的中文手册提供的是DemuxingProtocolCodecFactory; 2,,有的数据长度达到8K,网上有资料说 ...

  3. 由一篇文章引发的思考——多线程处理大数组

    今天领导给我们发了一篇文章文章,让我们学习一下. 文章链接:TAM - Threaded Array Manipulator 这是codeproject上的一篇文章,花了一番时间阅读了一下.文章主要是 ...

  4. atitit.sql server2008导出导入数据库大的表格文件... oracle mysql

    atitit.sql server2008导出导入数据库大的表格文件... 1. 超过80M的文件是不能在查询分析器中执行的 1 2. Oracle ,mysql大的文件导入 1 2.1. 使用sql ...

  5. linux文件分割(将大的日志文件分割成小的)

    linux文件分割(将大的日志文件分割成小的) linux下文件分割可以通过split命令来实现,可以指定按行数分割和安大小分割两种模式.Linux下文件合并可以通过cat命令来实现,非常简单. 在L ...

  6. 用Python对体积较大的CSV文件进行比较的经验

    用Python对体积较大的CSV文件进行比较的经验 » 进化的测试 | 进化的测试 用Python对体积较大的CSV文件进行比较的经验  python  Add comments 八 032010   ...

  7. linux文件分割(将大的日志文件分割成小的)【转载】

    linux文件分割(将大的日志文件分割成小的)linux下文件分割可以通过split命令来实现,可以指定按行数分割和安大小分割两种模式.Linux下文件合并可以通过cat命令来实现,非常简单. 在Li ...

  8. Java中使用POI读取大的Excel文件或者输入流时发生out of memory异常参考解决方案

    注意:此参考解决方案只是针对xlsx格式的excel文件! 背景 前一段时间遇到一种情况,服务器经常宕机,而且没有规律性,查看GC日志发生了out of memory,是堆溢出导致的,分析了一下堆的d ...

  9. virtuoso装载大的rdf文件的方法

    本文详细介绍了将一个比较大的rdf文件装载到virtuoso数据库的过程.参考virtuoso网站的文档说明,通过实践,将一个大约4.6G左右的nt文件装载到virtuoso数据库中,用了大概6个多小 ...

随机推荐

  1. 同源策略和Jsonp、CORS

    一.同源策略 同源策略(Same origin policy)是一种约定,它是浏览器最核心也最基本的安全功能,如果缺少了同源策略,则浏览器的正常功能可能都会受到影响.可以说Web是构建在同源策略基础之 ...

  2. Eclipse Common API

    Platform runtime Defines the extension point and plug-in model. It dynamically discovers plug-ins an ...

  3. wxpython wx.windows的API

    wx.Window is the base class for all windows and represents any visible object on screen. All control ...

  4. 有关在新版mac上 git 环境变量的配置问题

    前段时间买的新版 mpb ,各种环境什么都没有配置,想着在网上边搜边摸索着将各种开发工具逐步配置齐全,各种问题不断出现,不知道是不是新版的原因不兼容. 其中 git 的配置尤为奇怪.在git官网上直接 ...

  5. SQL Server ->> CLR编程问题汇总

    1) CLR不支持C#类方法重载(Method Overload) 今天打算写个枚举目录的C# CLR存储过程,结果发现原来CLR不支持方法重载. Msg 6572, Level 16, State ...

  6. PHP:使用php,循环html中的select标签与Php数据

    select标签,我们都知道是下拉列表,这里,我们使用foreach循环,将select中的数据进行输出 例子: 1.数据表:mimi_article,表中有个字段,为1或0,表示着是或否 2.通过p ...

  7. HTML:一个form表单有两个按钮,分别提交到不同的页面

    <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...

  8. python27 文件读写

    fileobject = open(文件的绝对路径或相对路径,读写模式,其他可选参数) '''r-读 文件不存在报错FileNotFoundError''' try: f = open('file0. ...

  9. Locust性能测试3 no-web运行

    Locust也支持no-web的方式运行,直接通过控制台设置并发用户数.每秒启动用户数.持续压测时间. locust -f 脚本路径 -c 用户数 -r 每秒启动用户数 --run-time 持续压测 ...

  10. 高效实时的网络会议数据传输库—UDT

    在视频会议系统的研发当中,我们的音.视频数据必须要有相应的可靠性作为保障,因为视频会议系统是一个实时性非常强的系统,如果其数据在网络不太好的情况下,有可能会出现丢包.数据延迟.数据堵塞等现象,出现这些 ...