php内存限制垃圾收集器

时间:2014-12-15 15:17:13

标签: php mysql memory garbage

3天撞到了墙上。

我为导入大文本文件开发了一个php脚本并填充了mysql数据库。直到我获得200万条记录才能完美运行,但我需要导入1000万行分为不同的文件。

我的应用程序扫描文件夹中的文件,获取文件扩展名(我有4种程序导入4种不同的扩展名)并调用相对导入功能。

我有一个由theese类组成的结构:

CLASS SUBJECT1{ public function import_data_1(){
    __DESTRUCT(){$this->childObject = null;}
    IMPORT SUBJECT1(){
    //fopen($file);
    //ob_start();
    //PDO::BeginTransaction();
    //WHILE (FILE) {
    //PREPARED STATEMENT        
    //FILE READING
    //GET FILE LINE
    //EXECUTE INSERT
    //} END WHILE
    //PDO::Commit();
    //ob_clean(); or ob_flush();
    //fclose($file);
    //clearstatcache();
   }
};}

CLASS SUBJECT2{ same as SUBJECT1;}

CLASS SUBJECT3{ same as SUBJECT1;}

CLASS SUBJECT4{ same as SUBJECT1;}

以及启动该过程的主类:

CLASS MAIN{
   switch($ext)
     case "ext1":
        $SUBJECT1 = new SUBJECT1();
        IMPORT_SUBJECT1();
        unset $SUBJECT1;
        $SUBJECT1 = null;
        break;
     case "ext2": //SAME AS CASE ext1 WITH IMPORT_SUBJECT2();
     case "ext3": //SAME AS CASE ext1 WITH IMPORT_SUBJECT3();
     case "ext4": //SAME AS CASE ext1 WITH IMPORT_SUBJECT4();

}

它与一些mysql文件缓冲区的调整完美配合(ib_logfile0和ib_logfile1设置为512Mb)。

问题是,每次程序终止时,php都不会释放内存。我确定调用了析构函数(我在__destruct方法中放了一个echo)并且该对象不可访问(var_dump说是NULL)。我尝试了很多方法来释放记忆,但现在我已经死了。

我也验证了     gc_collect_cycles() 在许多不同的代码点,它总是说0个循环所以所有abject都没有相互引用。 我甚至试图删除类结构并调用所有代码顺序,但我总是得到这个错误:

致命错误:第219行的C:\ php \ index.php内存不足(已分配511180800)(试图分配576个字节)(第219行是第13个文件中PS的执行)。

以这种方式使用内存:

  • php脚本:52MB
  • 结束第一个文件导入:110MB
  • destructors and unset calling:110MB
  • 新程序:110MB
  • 结束第二个文件导入250MB
  • destructors and unset calls:250MB
  • 新程序调用:250MB

因为你甚至可以看到未设置的物体,他们不会释放记忆。

我尝试将php ini内存大小设置为1024M,但它的增长速度非常快,并且在20个文件后崩溃。

有什么建议吗?

非常感谢!

编辑1:

发布代码:

class SUBJECT1{

    public function __destruct()
    {
        echo 'destroying subject1 <br/>';
    }

    public function import_subject1($file,$par1,$par2){
        global $pdo;

        $aux            = new AUX();
        $log            = new LOG();

// ---------------- FILES  ----------------
        $input_file    = fopen($file, "r");

// ---------------- PREPARED STATEMENT  ----------------

$PS_insert_data1= $pdo->prepare("INSERT INTO table (ID,PAR1,PAR2,PARN) VALUES (?,?,?,?) ON DUPLICATE KEY UPDATE ID = VALUES(ID), PAR1 = VALUES(PAR1), PAR2 = VALUES(PAR2), PAR3 = VALUES(PAR3), PARN = VALUES(PARN)");

$PS_insert_data2= $pdo->prepare("INSERT INTO table (ID,PAR1,PAR2,PARN) VALUES (?,?,?,?) ON DUPLICATE KEY UPDATE ID = VALUES(ID), PAR1 = VALUES(PAR1), PAR2 = VALUES(PAR2), PAR3 = VALUES(PAR3), PARN = VALUES(PARN)");

//IMPORT
if ($input_file) {
  ob_start();
  $pdo->beginTransaction();
  while (($line = fgets($input_file)) !== false) {
  $line = utf8_encode($line);
  $array_line = explode("|", $line);
  //set null values where i neeed
  $array_line = $aux->null_value($array_line);

  if(sizeof($array_line)>32){    
     if(!empty($array_line[25])){
          $PS_insert_data1->execute($array_line[0],$array_line[1],$array_line[2],$array_line[5]);
     }

  $PS_insert_data2->execute($array_line[10],$array_line[11],$array_line[12],$array_line[15]);
  }

$pdo->commit();    
flush();
ob_clean();
fclose($f_titolarita);
clearstatcache();
}

我为我的文件夹的所有文件执行此迭代,其他过程是相同的概念。 我仍然增加了内存,现在它因白页响应而崩溃: - \

2 个答案:

答案 0 :(得分:1)

就个人而言,我会对此略有不同。这些是我要做的步骤:

  • 打开PDO连接,在异常模式下设置PDO
  • 获取我想要阅读的文件列表
  • 创建一个可以利用PDO和文件列表并执行插入的类
  • 准备声明ONCE,多次使用
  • Chunk PDO事务提交50个(可配置)插入 - 这意味着我每50次调用$ stmt-&gt; execute(),我发出一个提交 - 它更好地利用了HDD,从而使其更快
  • 逐行阅读每个文件
  • 解析该行并检查其是否有效
  • 如果是,请添加到MySQL,如果没有 - 报告错误

现在,我已经创建了两个关于我如何去做的类和示例。我只测试了阅读部分,因为我不知道你的数据库结构,也不知道AUX()的作用。

class ImportFiles
{
    protected $pdo;
    protected $statements;
    protected $transaction = false;
    protected $trx_flush_count = 50; // Commit the transaction at every 50 iterations

    public function __construct(PDO $pdo = null)
    {
        $this->pdo = $pdo;

        $this->stmt = $this->pdo->prepare("INSERT INTO table 
                                                (ID,PAR1,PAR2,PARN)
                                            VALUES 
                                                (?,?,?,?) 
                                            ON DUPLICATE KEY UPDATE ID = VALUES(ID), PAR1 = VALUES(PAR1), PAR2 = VALUES(PAR2), PAR3 = VALUES(PAR3), PARN = VALUES(PARN)");
    }

    public function import($file)
    {
        if($this->isReadable($file))
        {
            $file = new FileParser($file);

            $this->insert($file);
        }
        else
        {
            printf("\nSpecified file is not readable: %s", $file);
        }
    }

    protected function isReadable($file)
    {
        return (is_file($file) && is_readable($file));
    }   

    protected function insert(FileParser $file)
    {
        while($file->read())
        {
            //printf("\nLine %d, value: %s", $file->getLineCount(), $file->getLine());

            $this->insertRecord($file);

            $this->flush($file);
        }

        $this->flush(null);
    }

    // Untested method, no idea whether it does its job or not - might fail
    protected function flush(FileParser $file = null)
    {
        if(!($file->getLineCount() % 50) && !is_null($file))
        {
            if($this->pdo->inTransaction())
            {
                $this->pdo->commit();

                $this->pdo->beginTransaction();
            }
        }
        else
        {
            if($this->pdo->inTransaction())
            {
                $this->pdo->commit();
            }
        }
    }   

    protected function insertRecord(FileParser $file)
    {
        $check_value = $file->getParsedLine(25);

        if(!empty($check_value))
        {
            $values = [ 
                $file->getParsedLine[0],
                $file->getParsedLine[1],
                $file->getParsedLine[2],
                $file->getParsedLine[5]
            ];
        }
        else
        {
            $values = [ 
                $file->getParsedLine[10],
                $file->getParsedLine[11],
                $file->getParsedLine[12],
                $file->getParsedLine[15]
            ];      
        }

        $this->stmt->execute($values);
    }
}

class FileParser
{
    protected $fh;
    protected $lineCount = 0;
    protected $line = null;
    protected $aux;

    public function __construct($file)
    {
        $this->fh = fopen($file, 'r');
    }

    public function read()
    {
        $this->line = fgets($this->fh);

        if($this->line !== false) $this->lineCount++;

        return $this->line;
    }

    public function getLineCount()
    {
        return $this->lineCount;
    }

    public function getLine()
    {
        return $this->line;
    }

    public function getParsedLine($index = null)
    {
        $line = $this->line;

        if(!is_null($line))
        {
            $line = utf8_encode($line);
            $array_line = explode("|", $line);

            //set null values where i neeed
            $aux = $this->getAUX();
            $array_line = $aux->null_value($array_line);

            if(sizeof($array_line) > 32)
            {   
                return is_null($index) ? $array_line : isset($array_line[$index]) ? $array_line[$index] : null;
            }
            else
            {
                throw new \Exception(sprintf("Invalid array size, expected > 32 got: %s", sizeof($array_line)));
            }
        }
        else
        {
            return [];
        }
    }

    protected function getAUX()
    {
        if(is_null($this->aux))
        {
            $this->aux = new AUX();
        }

        return $this->aux;
    }
}

用法:

$dsn = 'mysql:dbname=testdb;host=127.0.0.1';
$user = 'dbuser';
$password = 'dbpass';

try 
{
    $pdo = new PDO($dsn, $user, $password);

    $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);

    $import = new ImportFiles($pdo);

    $files = ['/usr/local/file1.txt', '/usr/local/file2.txt'];

    foreach($files as $file)
    {
        $import->import($file);
    }

} catch (Exception $e) 
{
    printf("\nError: %s", $e->getMessage());
    printf("\nFile: %s", $e->getFile());
    printf("\nLine: %s", $e->getLine());
}

答案 1 :(得分:0)

解决:

我做了这种方法,也许对有类似问题的人有用:

我打开了任务管理器并查看了这些情况下apache和mysql进程的内存使用情况:

  • 试图在不调用MySql程序的情况下阅读和详细说明文件(内存使用情况还可以)
  • 尝试阅读,详细说明并在db中插入具有扩展名的文件(所有.ext1,所有.ext2,....)
  • 使用大内存调试程序逐个增加隔离功能,找到有问题的隔离功能。
  • 发现问题并解决了

问题在于我调用函数作为参数准备好的声明。我认为,一旦准备好,它只是一个静态的&#34;要打电话的对象。会发生的情况是,如果你在一个函数中传递相同的PS,则内存会以指数方式增长。

希望这对某人有帮助。

再见!