
一.内存溢出解决方案
在做数据统计分析时,经常会遇到大数组,可能会发生内存溢出,这里分享一下我的解决方案。
假定日志中存放的记录数为500000条,那么解决方案如下:
ini_set(‘memory_limit’,’64M’); //重置php可以使用的内存大小为64M
一般在远程主机上是不能修改php.ini文件的,只能通过程序设置。
注:在safe_mode(安全模式)下,ini_set失效
set_time_limit(600);//设置超时限制为6分钟
$farr = $Uarr = $Marr = $IParr = $data = $_sub = array();
$spt = ”$@#!$”;
$root = ”/Data/webapps/VisitLog”;
$path = $dpath = $fpath = NULL;
$path = $root.”/”.date(“Y-m”,$timestamp);
$dpath = $path.”/”.date(“m-d”,$timestamp);
for($j=0;$j<24;$j++){
$v = ($j < 10) ? ”0″.$j : $j;
$gpath = $dpath.”/”.$v.”.php”;
if(!file_exists($gpath)){
continue;
} else {
$arr = file($gpath);////将文件读入数组中
array_shift($arr);//移出第一个单元-》<?php exit;?>
$farr = array_merge($farr,$arr);
unset($arr);
}
}
if(empty($this->farr)){
echo ”<p><center>没有相关记录!</center></p>”;
exit;
}
while(!empty($farr)){
$_sub = array_splice($farr, 0, 10000); //每次取出$farr中1000个
for($i=0,$scount=count($_sub);$i<$scount;$i++){
$arr = explode($spt,$_sub[$i]);
$Uarr[] = $arr[1]; //vurl
$Marr[] = $arr[2]; //vmark
$IParr[] = $arr[3].” |$nbsp;”.$arr[1]; //IP
}
unset($_sub);//用完及时销毁
}
unset($farr);
这里,不难看出,一方面,我们要增加PHP可用内存大小,另一方面,只要我们想办法对数组进行分批处理,分而治之,将用过的变量及时销毁(unset),一般是不会出现溢出问题的。
另外,为了节省PHP程序内存损耗,我们应当尽可能减少静态变量的使用,在需要数据重用时,可以考虑使用引用(&)。
再一点就是:数据库操作完成后,要马上关闭连接;
一个对象使用完,要及时调用析构函数(__destruct())。
二.unset销毁变量并释放内存问题
PHP的unset()函数用来清除、销毁变量,不用的变量,我们可以用unset()将它销毁。
但是某些时候,用unset()却无法达到销毁变 量占用的内存!
<?php
$s=str_repeat('1',255); //产生由255个1组成的字符串
$m=memory_get_usage(); //获取当前占用内存
unset($s);
$mm=memory_get_usage(); //unset()后再查看当前占用内存
echo $m-$mm;
?>
最后输出unset()之前占用内存减去unset()之后占用内存,如果是正数,那么说明unset(s从内存中销毁(或者 说,unset()之后内存占用减少了),可是我在PHP5和windows平台下,得到的结果是:0。
这是否可以说明,unset(s所占用内存的作用呢?我们再作下面的例子:
<?php
$s=str_repeat('1',256); //产生由256个1组成的字符串
$m=memory_get_usage(); //获取当前占用内存
unset($s);
$mm=memory_get_usage(); //unset()后再查看当前占用内存
echo $m-$mm;
?>
这个例子,和上面的例子几乎相同,唯一的不同是,s)已经将$s所占用的内存销毁了?
通过上面两个例子,我们可以得出以下结论:
结论一、unset()函数只能在变量值占用内存空间超过256字节时才会释放内存空间。
那么是不是只要变量值超过256,使用unset就可以释放内存空间呢?我们再通过一个例子来测试一下:
<?php
$s=str_repeat('1',256); //这和第二个例子完全相同
$p=&$s;
$m=memory_get_usage();
unset($s); //销毁$s
$mm=memory_get_usage();
echo $p.'<br />';
echo $m-$mm;
?>
刷新页面,我们看到第一行有256个1,第二行是0,按理说我们已经销毁了p只是引用
s)前后内存占用没变化!现在我们再做以下的例子:
<?php
$s=str_repeat('1',256); //这和第二个例子完全相同
$p=&$s;
$m=memory_get_usage();
$s=null; //设置$s为null
$mm=memory_get_usage();
echo $p.'<br />';
echo $m-$mm;
?>
现在刷新页面,我们看到,输出$p已经是没有内容了,unset()前后内存占用量之差是272,即已经清除了变量占用的内存。
本例中的$s=null也 可以换成unset(),如下:
<?php
$s=str_repeat('1',256); //这和第二个例子完全相同
$p=&$s;
$m=memory_get_usage();
unset($s); //销毁$s
unset($p);
$mm=memory_get_usage();
echo $p.'<br />';
echo $m-$mm;
?>
我们将p都使用unset()销毁,这时再看内存占用量之差也是272,说明这样也可以释放内存。
那么,我们可以得到另外一条结论:
结论二、只有当指向该变量的所有变量(如引用变量)都被销毁后,才会释放内存。
三.PHP大量数据循环时内存耗尽问题的解决方案(适用于导出大量数据时内存耗尽情况)
最近在开发一个PHP程序时遇到了下面的错误:
PHP Fatal error: Allowed memory size of 268 435 456 bytes exhausted
错误信息显示允许的最大内存已经耗尽。
遇到这样的错误起初让我很诧异,但转眼一想,也不奇怪,因为我正在开发的这个程序是要用一个foreach
循环语句在一个有4万条记录的表里全表搜索具有特定特征的数据,也就是说,一次要把4万条数据取出,然后逐条检查每天数据。
可想而知,4万条数据全部加载到内存中,内存不爆才怪。
毕竟编程这么多年,我隐约记得PHP里提供有非一次全部加载数据的API,是像处理流媒体那样,随用随取随丢、数据并不会积累在内存的查询方法。
经过简单的搜索,果然在官方网站上找到的正确的用法。
这个问题在PHP的官方网站上叫缓冲查询和非缓冲查询(Buffered and Unbuffered queries)。
PHP的查询缺省模式是缓冲模式。
也就是说,查询数据结果会一次全部提取到内存里供PHP程序处理。
这样给了PHP程序额外的功能,比如说,计算行数,将 指针指向某一行等。
更重要的是程序可以对数据集反复进行二次查询和过滤等操作。
但这种缓冲查询模式的缺陷就是消耗内存,也就是用空间换速度。
相对的,另外一种PHP查询模式是非缓冲查询,数据库服务器会一条一条的返回数据,而不是一次全部返回,这样的结果就是PHP程序消耗较少的内存,但却增加了数据库服务器的压力,因为数据库会一直等待PHP来取数据,一直到数据全部取完。
很显然,缓冲查询模式适用于小数据量查询,而非缓冲查询适应于大数据量查询。
对于PHP的缓冲模式查询大家都知道,下面列举的例子是如何执行非缓冲查询API。
非缓冲查询方法一: mysqli
<?php
$mysqli = new mysqli("localhost", "my_user", "my_password", "world");
$uresult = $mysqli->query("SELECT Name FROM City", MYSQLI_USE_RESULT);
if ($uresult) {
while ($row = $uresult->fetch_assoc()) {
echo $row['Name'] . PHP_EOL;
}
}
$uresult->close();
?>
非缓冲查询方法二: pdo_mysql
<?php
$pdo = new PDO("mysql:host=localhost;dbname=world", 'my_user', 'my_pass');
$pdo->setAttribute(PDO::MYSQL_ATTR_USE_BUFFERED_QUERY, false);
$uresult = $pdo->query("SELECT Name FROM City");
if ($uresult) {
while ($row = $uresult->fetch(PDO::FETCH_ASSOC)) {
echo $row['Name'] . PHP_EOL;
}
}
?>
非缓冲查询方法三: mysql
<?php
$conn = mysql_connect("localhost", "my_user", "my_pass");
$db = mysql_select_db("world");
$uresult = mysql_unbuffered_query("SELECT Name FROM City");
if ($uresult) {
while ($row = mysql_fetch_assoc($uresult)) {
echo $row['Name'] . PHP_EOL;
}
}
?>
四.大文件上传导内存溢出
报错情况:PHP Fatal error: Allowed memory size of 268 435 456 bytes exhausted
1.上传excel文件时,出现内存溢出的情况
在文件中分配大点的内存设置内存治标不治本,而且服务器的php.ini(memory_limit =
128M)有时候是很难改的。
所以在文件中设置。但是只有php.ini中的安全模式safe_mode开启时才可以设置
ini_set('memory_limit', '521M');
解决方法:
protected/extensions/ExcelHelper.php
$PHPReader->setReadDataOnly(true); //在拿到数据后进行设置只读
public static function importFromExcel($filePath, $blankRowDel = false)
{
set_time_limit(90);
Yii::import('application.extensions.phpexcel.PHPExcel');
$PHPExcel = new PHPExcel();
//默认用excel2007读取excel,若格式不对,则用之前的版本进行读取
$PHPReader = new PHPExcel_Reader_Excel2007();
if (!$PHPReader->canRead($filePath)) {
$PHPReader = new PHPExcel_Reader_Excel5();
if (!$PHPReader->canRead($filePath)) {
throw new Exception('can not read the excel file');
}
}
$PHPReader->setReadDataOnly(true);
$PHPExcel = $PHPReader->load($filePath);
$allSheetCount = $PHPExcel->getSheetCount();
$excelData = array();
for ($currentSheetNum = 0; $currentSheetNum < $allSheetCount; $currentSheetNum++) {
//读取excel文件中的第一个工作表
$currentSheet = $PHPExcel->getSheet($currentSheetNum);
//取得当前表名
$currentSheetTitle = $currentSheet->getTitle();
//取得最大的列号
$allColumn = $currentSheet->getHighestColumn();
//取得一共有多少行
$allRow = $currentSheet->getHighestRow();
// 从第一行获取列名
$currentRow = 1;
// 从第A列开始输出
$colunmNameArray = array();
$max_column_num = PHPExcel_Cell::columnIndexFromString($allColumn);
for ($current_column_num = 0; $current_column_num <= $max_column_num; $current_column_num++) {
$currentColumn = PHPExcel_Cell::stringFromColumnIndex($current_column_num);
$val = $currentSheet->getCellByColumnAndRow($current_column_num, $currentRow)->getValue();
if (empty($val)) {
continue;
}
if (is_object($val)) {
$colunmNameArray[$currentColumn] = '';
foreach ($val->getRichTextElements() as $cell) {
$colunmNameArray[$currentColumn] .= $cell->getText();
}
} else {
$colunmNameArray[$currentColumn] = $val;
}
}
//从第二行开始输出,因为excel表中第一行为列名
$sheetData = array();
for ($currentRow = 2; $currentRow <= $allRow; $currentRow++) {
//从第A列开始输出 */
$rowData = array();
$blankCell = 0;
for ($current_column_num = 0; $current_column_num <= $max_column_num; $current_column_num++) {
$currentColumn = PHPExcel_Cell::stringFromColumnIndex($current_column_num);
$val = $currentSheet->getCellByColumnAndRow($current_column_num, $currentRow)->getValue();
if (!isset($colunmNameArray[$currentColumn])) {
continue;
}
//如果输出汉字有乱码,则需将输出内容用iconv函数进行编码转换,如下将gb2312编码转为utf-8编码输出
if (is_object($val)) {
$rowData[$currentColumn] = '';
foreach ($val->getRichTextElements() as $cell) {
$rowData[$currentColumn] .= $cell->getText();
}
} else {
$rowData[$currentColumn] = $val;
}
if (empty($rowData[$currentColumn])) {
$blankCell++;
}
}
if (!$blankRowDel || chr($blankCell + 64) != $allColumn) {
$sheetData[] = $rowData;
}
}
$excelData[$currentSheetTitle] = array(
'header' => $colunmNameArray,
'content' => $sheetData,
);
}
return $excelData;
}
2.使用sql查询数据,查出来很多,导致内存溢出
sql语句在mysql中可以查询,但是使用php程序查询就报php内存溢出
(1)这个问题在php的官方网站叫缓冲查询和非缓冲查询。php的查询缺省模式是缓冲模式。也就是,查询数据结果一次全部提取到内存里供php程序额外的功能,比如说,计算行数,将指针指向某一行等。更重要的是程序对数据集反复进行二次查询和过滤操作。但这种缓冲查询模式的缺陷是消耗内存,也就是用空间换速度。
(2)另外一种查询模式是非缓冲查询,数据库服务器会一条一条的返回数据,而不是一次全部返回,这样的结果是php程序消耗较少的内存,但却增加了数据库服务器的压力,因为数据库会一直等待php来取数据,一直到数据全部取完。
1.首先查询数据库需要进行limit进行分页查询
2.如果不使用limit,使用非缓冲查询
1.mysql:
$conn = mysql_connect("localhost", "user", "pass");
$db = mysql_select_db("world");
$uresult = mysql_unbuffered_query("SELECT Name FROM City"); //非缓冲查询
if ($uresult) {
while ($row = mysql_fetch_assoc($uresult)) {
echo $row['Name'] . PHP_EOL;
}
}
2.pdo_mysql:
$pdo = new PDO("mysql:host=localhost;dbname=world", 'my_user', 'my_pass');
$pdo->setAttribute(PDO::MYSQL_ATTR_USE_BUFFERED_QUERY, false); //设置这个属性,就为非缓冲查询
$uresult = $pdo->query("SELECT Name FROM City");
if ($uresult) {
while ($row = $uresult->fetch(PDO::FETCH_ASSOC)) {
echo $row['Name'] . PHP_EOL;
}
}
3.mysqli:
$mysqli = new mysqli("localhost", "user", "password", "world");
$uresult = $mysqli->query("SELECT Name FROM City", MYSQLI_USE_RESULT);
if ($uresult) {
while ($row = $uresult->fetch_assoc()) {
echo $row['Name'] . PHP_EOL;
}
}
$uresult->close();
3.处理数组时出现内存溢出
(1)使用迭代生成器,可以通过继承Iterator接口实现
(2)使用关键词yield
function xrange($start, $end, $step = 1) {
for ($i = $start; $i <= $end; $i += $step) {
yield $i;
}
}
foreach (xrange(1, 1000000) as $num) {
echo $num, "\n";
}

网友评论