注意的问题点:


1、Excel

Excel2003版最大行数是65535行,Excel2007版的行数(1048576行)才能达到我们的要求,所以我们需要使用Excel的版本必须为2007版及以上,所以想几百万条轻轻松松一次性导入一张EXCEL表是不行的,你起码需要进行数据分割,保证数据不能超过104W一张表,PHPexcel内存溢出:既然数据限制在104W,那么就数据分割,于是你尝试50W一次导入表,然而PHPexcel内部有函数报内存溢出错误,然后你就不断的调小数据量,直到5W一次导入你都会发现有内存溢出错误。这是为什么呢,虽然你分割数据来导入多个数据表,但是最后PHPexcel内部还是一次性把所有表数据放进一个变量中来创建文件……额,这几百万数据一个变量存储,你想内存不溢出,还真有点困难。 
(PHPExcel也有解决方案,PHPExcel_Settings::setCacheStorageMethod方法更改缓冲方式来减小内存的使用)


2、超时问题


     脚本允许运行的时间,一般默认为30秒,如果超过了此设置,脚本返回一个致命的错误。
     方法一
           在php.ini可以通过定义max_execution_time来设置PHP页面的最大执行时间。
     方法二
            在脚本中添加set_time_limit(0);
            这个函数指定了当前所在php脚本的最大执行时间,假设为800秒,实际上 最大执行时间=php.ini里的max_execution_time数值 - 当前脚本已经执行的时间 + 设定值
    假如php.ini里的max_execution_time=30,当前脚本已经执行5秒,则:
        最大执行时间=30-5+800=825秒。
             括号里边的数字是执行时间,如果为零说明永久执行直到程序结束,如果为大于零的数字,则不管程序是否执行完成,到了设定的秒数,程序结束。
             注意:这个函数的运行需要你关闭安全模式,在php.ini中将safe_mode = Off 安全模式设置为Off,否则将会出现错误


3、内存溢出问题


       php默认内存限制是128M,如果一次性的把所有数据从数据库取出填充到内存中,一下不光内存存储不够,并且如果服务器CPU配置不高的话使用率一下也能达到100%,会造成服务器卡负载。可以从两个方向解决:
      一 配置方向,解决方法:
                方法一  直接在程序中添加代码:
                       ini_set('memory_limit', '1024M')。
                方法二  修改php.ini配置文件
                       查找到memory_limit = 128M这一行,将128M改大点。
                       重启服务器。
                方法三  修改 .htaccess
                       php_value memory_limit XXX M ;

                注意: php内存溢出时,memory_limit设置太大会影响系统速度,因为系统和数据库及其他程序同样需要内存空间,一般系统和数据库内存空间是自己分配的
      二  代码中用yield生成器(从PHP 5.5开始引入的)来处理内存溢出。具体可以参考一篇大神的文章PHP百万级数据导出方案(生成器直接输出单个CSV)


4、缓存(buffer)过多


       PHP也有自己的buffer机制。默认是开启的,大小默认4096(4kb),在php.ini配置文件中由output_buffering配置。当执行php执行echo,print的时候,是先将数据写入php的buffer,当一个php buffer写满的时候,脚本进程会将php 的buffer数据发送给系统内核交由tcp传给浏览器显示,数据流程:
    echo/pring -> php buffer -> tcp buffer (服务器系统buffer)-> 浏览器 buffer ->浏览器展示

     当你用PHP原生函数putcsv()其实就使用到了输出缓存buffer,如果你把几百万的数据一直用这个函数输出,会导致输出缓存太大而报错的,因此我们每隔一定量的时候,必须进行将输出缓存中的内容取出来,设置为等待输出状态。可以在代码中添加:
     

ob_flush();  //把数据从PHP的缓冲(buffer)中释放出来
flush();       //把不在缓冲(buffer)中的或者说是被释放出来的数据发送到浏览器


分析完上面的注意点,假设数据量是几百万,我们可以从以下几个方向进行优化:

  • 从数据库中进行数据量分批读取读取,以防变量内存溢出,
  • 尽量选择数据保存文件格式是csv文件,以方便导出之后的阅读、导入数据库等操作。
  • 以防不方便excel读取csv文件,我们需要104W之前就得把数据分割进行多个csv文件保存
  • 多个csv文件输出给用户下载是不友好的,我们还需要把多个csv文件进行压缩,最后提供给一个ZIP格式的压缩包给用户下载就好。


另外其他相关信息:
      

CSV的Excel支持GBK编码,一定要转换,否则乱码


使用linux命令 ps 或 top 命令查看进程时, 能看到内存消耗的百分比和大小
查看php运行目录命令:
which php
/usr/bin/php

ps aux | grep -c php-fpm      //查看php-fpm进程数
/usr/bin/php  -i|grep mem   //查看运行内存
/etc/init.d/php-fpm restart   //重启php-fpm

memory_get_usage 指当前脚本正在使用的内存, memory_get_peak_usage 返回分配给 PHP 内存的峰值, 举例说明:
1、 数据库读出来千万条数据,假如说需要消耗100MB,那么系统会分配给进程 100MB
2、当处理完数据后 unset 掉, 其实当前进程的消耗的内存并不会变小, 即不会释放100MB空间。unset后可以让 memory_get_peak_usage 不继续升高, 程序执行被分配的内存(进程里的内存)仍然要以 memory_get_peak_usage 为准
3、内存被分划为, "已使用" 和 "空闲", unset 只会把 "已使用" 变为 "空闲", 下次内存请求时会先去"空闲"里取, 程序结束, GC 才会释放全部内存


ob_start() : 激活php output_buffering机制,在不写参数情况下,ob_start()将php buffer空间设置
到了足够大 ,只有脚本执行结束,或者调用ob_end_flush函数,才会把数据发送给客户端浏览器。ob_start()函数内的参数可以设置output_buffering大小及输出机制。具体可以查看手册。
ob_end_flush与ob_end_clean:都会关闭ouput_buffering机制。不同的是,ob_end_flush只是把php buffer中的数据传送到浏览器,而ob_clean_clean将php bufeer中的数据清空,但不发送给客户端浏览器。