php获取大数据

2018-07-18 15:42:08 hu_feng903 阅读数 2196

开发中经常遇到,格式化导出数据库中源数据。各种框架中也有很多好用的包,直接获取数据,丢入对应的处理方法即可。
但是假如遇到导出大量的数据,特别还需要进行数据处理。一般处理大数据有如下几种解决办法:
这里写图片描述

这里主要记录下使用fputcsv方式导出数据。

public function exportData()
    {   
        set_time_limit(0);
        ini_set('memory_limit', '1024M');

        $columns = [
            '列名1', '列名2', '列名3'      //需要几列,定义好列名
        ];

        //设置好告诉浏览器要下载excel文件的headers
        header('Content-Description: File Transfer');
        header('Content-Type: application/vnd.ms-excel');
        header('Content-Disposition: attachment; filename="导出数据-'.date('Y-m-d', time()).'.csv"');
        header('Expires: 0');
        header('Cache-Control: must-revalidate');
        header('Pragma: public');
        $fp = fopen('php://output', 'a');//打开output流
        mb_convert_variables('GBK', 'UTF-8', $columns);
        fputcsv($fp, $columns);//将数据格式化为CSV格式并写入到output流中

        //添加查询条件,获取需要的数据
        $query = Model::class()->where();

        //获取总数,分页循环处理
        $accessNum = $query->count();
        $perSize = 1000;
        $pages   = ceil($accessNum / $perSize);

        for($i = 1; $i <= $pages; $i++) {

            $db_data = $query->limit($perSize)->offset(($i-1)*$perSize)->get();

            foreach($db_data as $key => $value) {
                $rowData = [];

                //获取每列数据,转换处理成需要导出的数据
                //需要格式转换,否则会乱码
                mb_convert_variables('GBK', 'UTF-8', $rowData);
                fputcsv($fp, $rowData);
            }

            //释放变量的内存
            unset($db_data);

            //刷新输出缓冲到浏览器
            ob_flush();

            //必须同时使用 ob_flush() 和flush() 函数来刷新输出缓冲。
            flush();
        }

        fclose($fp);
        exit();
    }

这样会一边生成,一边写入下载文件,提高处理速度。
之前使用的包处理,生成一个25M的数据,要20分钟。这种方式处理需要1分多钟(也和实际处理的环境有关)。

参考内容:
https://segmentfault.com/a/1190000011663425
http://www.php.net/manual/en/function.fputcsv.php

2018-03-09 11:20:17 fastjack 阅读数 8600

问题(来自lunacyfoundme)

       我正在重建我们公司内部网,期间遇到一个与大量数据处理报告有关的前一个版本的问题。此前我曾用同步处理程序代码解决过这个问题,只是运行的很慢很慢,这导致我不得不延长最大脚本运行时间10到15分钟。有没有更好的方式来处理PHP站点里的大量数据呢?理想情况下我想在后台运行它,并且跑的越快越好。这个过程包括处理成千上万条的财务数据,我是使用Laravel来重建这个站点的。
 

最好受欢迎的回答(来自spin81):

       人们都告诉你要使用队列和诸如此类的东西,这是一个好主意,但问题好像并没有出在PHP上面。Laravel/OOP是很厉害的,但生成你所说的报告的程序似乎不应你该有问题。对于不同的看法,我想看看你得到这些数据时使用的SQL查询。正如其他人所说,如果你的表单有成千上万行那你的报告应该不会耗费10到15分钟才完成。实际上,如果你没做错事的话可能会在一分钟内就能处理成千上万条记录,完成同样的一篇报告。

1.如果你正在做成千上万条查询,看看你能不能先只做几条查询。我之前曾使用一个PHP函数把70000条查询降为十几条查询,这样它的运行时间就从几分钟降到了几分之一秒。
 
2.在你的查询上运行EXPLAIN,看看你是不是缺少什么索引。我曾经做过一个查询,通过增加了一个索引后效率提高了4个数量级,这没有任何夸张的成分。如果你正在使用MySQL,你可以学学这个,这种“黑魔法”技能会让你和你的小伙伴惊呆的。
 
3.如果你正在做SQL查询,然后获得结果,并把很多数字弄到一起,看看你能不能使用像SUM()和AVG()之类的函数调用GROUP BY语句。跟普遍的情况下,让数据库处理尽量多的计算。我能给你的一点很重要的提示是:(至少在MySQL里是这样)布尔表达式的值为0或1,如果你很有创意的话,你可以使用SUM()和它的小伙伴们做些很让人惊讶的事情。
 
4.好了,最后来一个PHP端的提示:看看你是不是把这些同样很耗费时间的数字计算了很多遍。例如,假设1000袋土豆的成本是昂贵的计算,但你并不需要把这个成本计算500次,然后才把1000袋土豆的成本存储在一个数组或其他类似的地方,所以你不必把同样的东西翻来覆去的计算。这个技术叫做记忆术,在像你这样的报告中使用往往会带来奇迹般的效果。
 
原文:http://www.reddit.com/r/PHP/comments/2pyuy0/heavy_data_processing_in_php/

译文:http://www.php100.com/html/dujia/2014/1226/8195.html


2020-03-25 18:07:46 u014559227 阅读数 178

在日常生活中,我们需要在控制台让php运行某些程序来进行日常的维护消耗。

在某些情况下,工作任务中需要从数据库读出大量的数据来进行后续的处理,那么php来处理这些大量的数据怎么办呢?
php 的运行内存一般设置的是128M,当读出的数据量大小超出128M程序运行就会抛出异常(php 连接mysql并从mysql中读出的数据一般会直接读进内存当中)。想到的解决办法首先是增大php的运行内存,但有些情况下这种方案是治标不治本的方法,那么我们怎么一劳永逸的去解决呢?

将从mysql中读取的数据不读进内存中,放置在mysql的缓冲区中(这样对数据库的压力比较大,因为查询出来的大数据并没有读进php运行内存),php执行后获得一个资源标识符,通过指标的方式去获取其相关的数据。

//PDO 关键设置,如果不设置,php依旧会从pdo一次取出数据到php
$dbh->setAttribute(\PDO::MYSQL_ATTR_USE_BUFFERED_QUERY, false);
2014-07-08 11:16:48 samxx8 阅读数 1068

php读取大文件详解【OK】  

2010-08-03 10:10:57|  分类: WEB网站性能优化 |  标签:fp  file  fseek  tail  php   |举报 |字号 订阅

在php中,对于文件的读取时,最快捷的方式莫过于使用一些诸如file、file_get_contents之类的函数,简简单单的几行代码就能很漂亮的完成我们所需要的功能。但当所操作的文件是一个比较大的文件时,这些函数可能就显的力不从心, 下面将从一个需求入手来说明对于读取大文件时,常用的操作方法。

需求如下:
  现有一个1G左右的日志文件,大约有500多万行, 用php返回最后几行的内容。

实现方法:

1. 直接采用file函数来操作

注:由于 file函数是一次性将所有内容读入内存,而php为了防止一些写的比较糟糕的程序占用太多的内存而导致系统内存不足,使服务器出现宕机,所以默认情况下限制只能最大使用内存16M,这是通过php.ini里的memory_limit = 16M来进行设置,这个值如果设置-1,则内存使用量不受限制.

下面是一段用file来取出这具文件最后一行的代码.
整个代码执行完成耗时 116.9613 (s).
php对大文件进行读取操作【OK】 - PHP程序员 - 李国华【PHP程序员C++】博客
 
$fp = fopen($file, "r");
$num = 10;
$chunk = 4096;
$fs = sprintf("%u", filesize($file));
$max = (intval($fs) == PHP_INT_MAX) ? PHP_INT_MAX : filesize($file);
for ($len = 0; $len < $max; $len += $chunk) {
  $seekSize = ($max - $len &gt; $chunk) ? $chunk : $max - $len;
    fseek($fp, ($len + $seekSize) * -1, SEEK_END);
    $readData = fread($fp, $seekSize) . $readData;

    if (substr_count($readData, "\n") &gt;= $num + 1) {
        preg_match("!(.*?\n){".($num)."}$!", $readData, $match);
        $data = $match[0];
        break;
    }
}
fclose($fp);
echo $data;

我机器是2个G的内存,当按下F5运行时,系统直接变灰,差不多20分钟后才恢复过来,可见将这么大的文件全部直接读入内存,后果是多少严重,所以不在万不得以,memory_limit这东西不能调得太高,否则只有打电话给机房,让reset机器了.

2.直接调用linux的tail命令来显示最后几行

在linux命令行下,可以直接使用tail -n 10 access.log很轻易的显示日志文件最后几行,可以直接用php来调用tail命令,执行php代码如下.
整个代码执行完成耗时 0.0034 (s)
php对大文件进行读取操作【OK】 - PHP程序员 - 李国华【PHP程序员C++】博客
 
file = 'access.log';
$file = escapeshellarg($file); // 对命令行参数进行安全转义
$line = `tail -n 1 $file`;
echo $line;

3. 直接使用php的fseek来进行文件操作


这种方式是最为普遍的方式,它不需要将文件的内容全部读入内存,而是直接通过指针来操作,所以效率是相当高效的.在使用fseek来对文件进行操作时,也有多种不同的方法,效率可能也是略有差别的,下面是常用的两种方法.
方法一:
首先通过fseek找到文件的最后一位EOF,然后找最后一行的起始位置,取这一行的数据,再找次一行的起始位置,再取这一行的位置,依次类推,直到找到了$num行。
实现代码如下
整个代码执行完成耗时 0.0095 (s)
php对大文件进行读取操作【OK】 - PHP程序员 - 李国华【PHP程序员C++】博客
 
function tail($fp,$n,$base=5)
{
    assert($n>0);
    $pos = $n+1;
    $lines = array();
    while(count($lines)< =$n){
        try{
            fseek($fp,-$pos,SEEK_END);
        } catch (Exception $e){
            fseek(0);
            break;
        }
        $pos *= $base;
        while(!feof($fp)){
            array_unshift($lines,fgets($fp));
        }
    }
    return array_slice($lines,0,$n);
}
var_dump(tail(fopen("access.log","r+"),10));

方法二:

还是采用fseek的方式从文件最后开始读,但这时不是一位一位的读,而是一块一块的读,每读一块数据时,就将读取后的数据放在一个buf里,然后通过换行符(\n)的个数来判断是否已经读完最后$num行数据.
实现代码如下
整个代码执行完成耗时 0.0009(s).
php对大文件进行读取操作【OK】 - PHP程序员 - 李国华【PHP程序员C++】博客
 
$fp = fopen($file, "r");
$line = 10;
$pos = -2;
$t = " ";
$data = "";
while ($line > 0) {
    while ($t != "\n") {
        fseek($fp, $pos, SEEK_END);
        $t = fgetc($fp);
        $pos --;
    }
    $t = " ";
    $data .= fgets($fp);
    $line --;
}
fclose ($fp);
echo $data

方法三:


整个代码执行完成耗时 0.0003(s)
php对大文件进行读取操作【OK】 - PHP程序员 - 李国华【PHP程序员C++】博客
 
ini_set('memory_limit','-1');
$file = 'access.log';
$data = file($file);
$line = $data[count($data)-1];
echo $line;
2019-01-05 09:45:30 HZX19941018 阅读数 13293

       博主昨天在实现一个需求,因为需要用到大数据导出,所以自己动手写了一个导出方法,因为要考虑到大数据的导出,所以不能一次性读取数据库,想到了分页获取数据,写进导出缓存中,这样避免了一次性从数据库中读取大量数据而造成奔溃,思路就是分页获取数据,写进导出数组中,同时清除查询数据的缓存,这样就可以避免奔溃,下面向大家分享有关教程

       博主用的是原生方法写,跟之前另一篇博客也是很相似的,大家有兴趣也可以看看(传送门)  ,我们先来封装我们的数据库查询方法,等下会用,代码如下:

/**
 * 获取导出数据  
 * @param $getLinkClearSQl  数据库语句
 */
function getReturnData($getLinkClearSQl)
{
    $con = mysqli_connect("链接地址", "账号名", "密码", "数据库名");
    $getLinkClearingData = mysqli_query($con, $getLinkClearSQl);
    $returnData = array();
    //循环组合即将导出数据
    while ($rowData = mysqli_fetch_array($getLinkClearingData)) {
        $returnData[] = array(
            'day' => date('Y-m-d', strtotime($rowData["day"])) . ' ', 
            'num1' => $rowData["num1"],
            'num2' => $rowData["num2"],
            'num3' => $rowData["num3"],
            'num4' => $rowData["num4"],
        );
    }
    //返回已封装好的数据
    return $returnData;
}
      封装好导出数据,然后再来封装我们的私有方法,该方法能够使得导出的中文不乱码,代码如下:

function fputcsv2($handle, array $fields, $delimiter = ",", $enclosure = '"', 
    $escape_char = "\\")
{
    foreach ($fields as $k => $v) {
        $fields[$k] = iconv("UTF-8", "GB2312//IGNORE", $v);  // 这里将UTF-8转为GB2312编码
    }
    fputcsv($handle, $fields, $delimiter, $enclosure, $escape_char);
}

       接下来是我们的主体了,上面我们已经封装了两个方法,跟主体区分开,看起来会比较舒服,代码如下:

//设置时间跟最大限制
set_time_limit(0);
ini_set("memory_limit", "512M");
#设置文件名以及列名
$fileName = "导出数据";
$columnName = array('日期','字段1','字段2','字段3','字段4',); 
//设置头部
header('Content-Type: application/vnd.ms-excel;charset=utf-8');
header("Content-Disposition:filename=" . $fileName . ".csv");
// 打开PHP文件句柄,php://output 表示直接输出到浏览器
$fp = fopen('php://output', 'a');
// 将中文标题转换编码,否则乱码
foreach ($columnName as $i => $v) {
    $columnName[$i] = $v;
}
//每次获取数量
$pre_count = 100;
// 将标题名称通过fputcsv写到文件句柄
fputcsv2($fp, $columnName);
$rows = array();
//循环读取数据并导出数据
for ($t = 0; $t < intval($linkClearingNumber / $pre_count) + 1; $t++) {
    #获取页码
    $pageNumber = $t * $pre_count;
    #重构SQL
    $getLinkClearSQl = "SELECT * FROM 表名"." limit $pageNumber,$pre_count";
    #获取数据
    $export_data = getReturnData($linkData, $getLinkClearSQl);
    #循环获取存放数据
    foreach ($export_data as $item) {
        $rows = array();
        foreach ($item as $export_obj) {
            $rows[] = iconv('utf-8', 'GB18030', $export_obj);
        }
        fputcsv($fp, $rows);
    }
    // 将已经写到csv中的数据存储变量销毁,释放内存占用
    unset($export_data);
    ob_flush();
    flush();
}
exit ();

        这样我们便完成了导出上万数据而不会造成网络奔溃的方法,各位小伙伴可以尝试尝试。

        更多文章请关注微信公众号