[原文来自于转载, 但他的结论不太正确, 尤其对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);//销毁变量
从结果上看是没有效果的,原来执行不了的仍然是执行不了,我加上些断点和打印信息来辅助排查为什么没有达到预期。
 
以下是我实际项目去做的测试,代码如下
  1. <?php
  2. set_time_limit(0);
  3. ini_set('memory_limit', '1024M');//视自身业务情况,这里临时分配足够内存去测试
  4.  
  5. echo "\r\nstart:" . memory_get_usage();
  6. $res = file_get_contents("./content.txt"); //文件里的是30W行整数
  7. $rs = explode("\n", $res);         //这两段可当做是上面的大量的sql查询的结果
  8.  
  9. echo "\r\nbefore-data:" . memory_get_usage();
  10. $num = count($rs);
  11. $sum = 0;
  12. foreach($rs as $k=>$value) {
  13. //计算实现逻辑
  14. $sum += $value;
  15. if($k % 50000 == 0 ){
  16.   echo "\r\ncount:".count($rs);
  17.   echo "\r\nafterMemory:" . memory_get_usage();
  18. }
  19. unset($rs[$k]);
  20. }
    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前加个 "&" 符号再来测试发现就可以立即释放内存了,因为&符表引用
 
  1. foreach($rs as $k=> &$value ) {
  2. //计算实现逻辑
  3. $sum += $value;
  4. if($k % 50000 == 0 ){
  5. echo "\r\ncount:".count($rs);
  6. echo "\r\nafterMemory:" . memory_get_usage();
  7. }
  8. unset($rs[$k]);
  9. }
[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()的话会立即释放内存.
  1. for ($k=0; $k < $num; $k++) {
  2. $value = $rs[$k];
  3. //计算实现逻辑
  4. $sum += $value;
  5. if($k % 50000 == 0 ){
  6. echo "\r\ncount:".count($rs);
  7. echo "\r\nafterMemory:" . memory_get_usage();
  8. }
  9. unset($rs[$k]);
  10. }
[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. Jmeter压力测试入门操作

    Jmeter压力测试入门   1. 前言 Jmeter 是Apache组织开发的基于Java的压力测试工具,开源并且支持多个操作系统,是一款很好的HTTP测试工具.本篇文章主要的目的是帮助没有接触过J ...

  2. 互联网轻量级框架SSM-查缺补漏第一天

    简言:工欲其事必先利其器,作为一个大四的准毕业生,在实习期准备抽空补一下基础.SSM框架作为互联网的主流框架,在会使用的基础上还要了解其原理,我觉得会对未来的职场会有帮助的.我特意的买了一本<J ...

  3. How can I use Server.MapPath() from global.asax?

    引用: using System.Web.Hosting; string filePathcsv = ""; string file = ""; string ...

  4. mardown文档的用法

    <hr>分割换行符<br>分隔符 <ul> <li></li></ul>无序标签 <ol><li>< ...

  5. TortoiseGit客户端安装及使用(上传代码到git@osc

    用TortoiseGit客户端之前要下载安装git http://git-scm.com/download/win 下载完安装完就可以了,之后好像就用不上了,全交给TortoiseGit客户端了 第一 ...

  6. Mysql 服务无法启动解决办法

    1.我使用的是MySQL-5.7.10-winx64 版本,在安装后启动服务时出现 “服务无法启动”错误 2.解决办法为删除安装目录中的data文件,然后打开cmd调到自己的安装目录下输入mysqld ...

  7. mif文件C语言生成

    1:正弦波 用函数 sin (x * π/180°) /************************************************** 正弦波 mif 生成 ********** ...

  8. 初识EMC

    EMC,即电磁兼容,是指设备在预期的电磁环境中,能按设计要求正常抵抗电磁干扰的能力.其主要包含3个方面:电磁干扰(EMI),电磁抗扰(EMS)与静电放电抗扰(ESD). 电磁干扰的方式可以大概分为传导 ...

  9. 一个简单的例子理解Kubernetes的三种IP地址类型

    很多Kubernetes的初学者对Kubernetes里面三种不同的IP地址和工作机制理解得不是很清楚. 本文我们通过一个最简单的例子来学习. 用如下命令行创建一个基于nginx的deployment ...

  10. The Tao to Excellent 2

    就算自己现在的技术水平很菜,就算自己现在写的代码还是很烂,但我们还是要一直坚持在最前线,一直向上,也许,在前方,就有不一样的风景在等着我们. 因为我深深明白到一件事:人之所以选择混吃等死,是因为他们根 ...