使用PHP&;MySQL。。。如何释放内存?

使用PHP&;MySQL。。。如何释放内存?,php,mysql,performance,memory-leaks,database-performance,Php,Mysql,Performance,Memory Leaks,Database Performance,要求: 我们在两台服务器中有两个类似的表。服务器中第一个具有唯一键列A、B、C的表,我们将表1行插入到具有唯一键列B、C、D的表2中 由于不同的唯一键列约束,表1将有大约500万行,表2将插入大约300万行 这里的要求是从表1中获取所有行,如果表2中不存在相同的记录,则插入到表2中,如果记录匹配,则增加计数并更新表2中的“cron_modified_date”列 对于这个设置,PHP版本是5.5,MySQL版本是5.7,DB服务器有6GB的RAM 当在脚本下执行时,在处理了200万条记录之后,处

要求:

我们在两台服务器中有两个类似的表。服务器中第一个具有唯一键列A、B、C的表,我们将表1行插入到具有唯一键列B、C、D的表2中

由于不同的唯一键列约束,表1将有大约500万行,表2将插入大约300万行

这里的要求是从表1中获取所有行,如果表2中不存在相同的记录,则插入到表2中,如果记录匹配,则增加计数并更新表2中的“cron_modified_date”列

对于这个设置,PHP版本是5.5,MySQL版本是5.7,DB服务器有6GB的RAM

当在脚本下执行时,在处理了200万条记录之后,处理速度变得非常慢,RAM没有释放出来,有时所有的RAM都被脚本消耗掉,之后脚本就根本不处理了

如您所见,我正在重置变量并关闭DB连接,但这并没有释放DB服务器RAM。经过一些阅读,我了解到,可能是PHP垃圾收集需要手动调用来释放资源,但它也不能释放RAM

我在这里做错了什么,以及如何使用PHP、MYSQL处理数百万条记录

在执行脚本时,是否有其他方法释放RAM,以便脚本能够与执行竞争

/* Fetch records count for batch insert*/

$queryCount = "SELECT count(*) as totalRecords FROM TABLE1 where created_date > = '2018-02-10'";
$rowsCount = $GLOBALS['db']->execRaw( $queryCount)->fetchAll();

$recordsPerIteration = 50000 ;
$totalCount = $rowsCount[0]['totalRecords']; 
$start = 0;

gc_disable() ;
if ( $totalCount > 0 ) {
    while ( $totalCount > 0 ) {
    $query = "SELECT *  FROM TABLE1
                WHERE where created_date > = '2018-02-10'
                ORDER BY suggestion_id DESC 
                LIMIT ".$start.",".$recordsPerIteration;

    print "sql is $query" ;

    $getAllRows = $GLOBALS['db']->execRaw( $query )->fetchAll();
    $GLOBALS['db']->queryString = null;
    $GLOBALS['db']->close() ;

    foreach ($getAllRows as  $getRow) {

        $insertRow  = " INSERT INTO TABLE2 (
                            Name,
                            Company,
                            ProductName,
                            Status,
                            cron_modified_date)
                VALUE (   
                            ".$GLOBALS['db_ab']->quote($getRow['Name']).", 
                            ".$GLOBALS['db_ab']->quote($getRow['Company']).", 
                            ".$GLOBALS['db_ab']->quote($getRow['ProductName']).",
                            ".$getRow['Status'].",
                            ".$GLOBALS['db_ab']->quote($getRow['created_date'])."
                        )
                    ON DUPLICATE KEY UPDATE count = (count + 1) , cron_modified_date =  '".$getRow['created_date']."'" ;

                $GLOBALS['db_ab']->execRaw( $insertRow ) ;
                $GLOBALS['db_ab']->queryString = null;
                $getRow = null;
                $insertRow = null;
                $GLOBALS['db_ab']->close() ;
           }
          gc_enable() ;
          $totalCount   = $totalCount- $recordsPerIteration;
          $start        += $recordsPerIteration ;
          $getAllRows = null;
          gc_collect_cycles() ;
    }

}
解决方案
根据@ABelikov提供的建议和一些命中跟踪方法。。。最后,下面的代码工作得非常好,在每插入50K条记录后释放RAM

以下是主要调查结果

  • 在每次涉及大数据操作的主要操作后释放DB connections变量,并重新连接DB,以便刷新DB缓冲区
  • 插入语句并一次性执行插入。不要在循环中执行单记录插入

    谢谢大家的宝贵建议和帮助

    /* Fetch records count for batch insert*/
    
    
    $queryCount = "SELECT count(*) as totalRecords FROM TABLE1 where created_date > = '2018-02-10'";
    $rowsCount = $GLOBALS['db']->execRaw( $queryCount)->fetchAll();
    
    $recordsPerIteration = 50000 ;
    $totalCount = $rowsCount[0]['totalRecords']; 
    $start = 0;
    
    if ( $totalCount > 0 ) {
       while ( $totalCount > 0 ) {
           $query = "SELECT *  FROM TABLE1
                WHERE where created_date > = '2018-02-10'
                ORDER BY suggestion_id DESC 
                LIMIT ".$start.",".$recordsPerIteration;
    
    print "sql is $query" ;
    
    $getAllRows = $GLOBALS['db']->execRaw( $query )->fetchAll();
    $GLOBALS['db']->queryString = null;
    $GLOBALS['db']->close() ;
    
    $insertRow  = " INSERT INTO TABLE2 (
                            Name,
                            Company,
                            ProductName,
                            Status,
                            cron_modified_date)
                VALUE (  " ;
    
    
    foreach ($getAllRows as  $getRow) {
    
    
            $insertRow  .= (".$GLOBALS['db_ab']->quote($getRow['Name']).", 
                            ".$GLOBALS['db_ab']->quote($getRow['Company']).", 
                            ".$GLOBALS['db_ab']->quote($getRow['ProductName']).",
                            ".$getRow['Status'].",
                            ".$GLOBALS['db_ab']->quote($getRow['created_date'])."),";
            }
    
    $insertRow=rtrim($insertRow,','); // Remove last ','
    $insertRow.= " ON DUPLICATE KEY UPDATE count = (count + 1) , cron_modified_date =  '".$getRow['created_date']."'" ;
    
    $GLOBALS['db_ab']->execRaw( $insertRow ) ;              
    //Flushing all data to freeup RAM
    $GLOBALS['db_ab'] = null ;
    $GLOBALS['db'] = null ;
    $insertRow = null;
    
    $totalCount = $totalCount- $recordsPerIteration;
    $start      += $recordsPerIteration ;
    $getAllRows = array();
    $getAllRows = null;
    print " \n Records needs to process ".$totalCount."\n";
    
    }
    
    }
    

1.插入多行解决方案

您可以使用“插入多行”加快脚本速度,请参见此处

在tbl_名称中插入(a、b、c)值(1,2,3)、(4,5,6)、(7,8,9)

您只需要将值保留在foreach中的一部分,并移出所有其他值

 $insertRow  = " INSERT INTO TABLE2 (
                             Name,
                             Company,
                             ProductName,
                             Status,
                             cron_modified_date) VALUES ";
 foreach ($getAllRows as  $getRow) {
     $insertRow.="(".$GLOBALS['db_ab']->quote($getRow['Name']).",
                   ".$GLOBALS['db_ab']->quote($getRow['Company']).", 
                   ".$GLOBALS['db_ab']->quote($getRow['ProductName']).",
                   ".$getRow['Status'].",
                   ".$GLOBALS['db_ab']->quote($getRow['created_date'])."),";

 }
 $insertRow=rtrim($insertRow,','); // Remove last ','
 $insertRow .= " ON DUPLICATE KEY UPDATE count = (count + 1) , cron_modified_date =  '".$getRow['created_date']."'" ;
 $GLOBALS['db_ab']->execRaw( $insertRow ) ;
 $GLOBALS['db_ab']->queryString = null;
 $getRow = null;
 $insertRow = null;
 $GLOBALS['db_ab']->close() ;
只有当您的foreach“body”通常运行不止一次时,这才有帮助

2.MySQL服务器端解决方案

尝试使用事务处理

只需在脚本开始时开始一个脚本,并在脚本结束时提交。 取决于你的服务器,这真的很有帮助。 但是要小心!这取决于您的MySQL服务器配置集。需要测试。

  • PHP在内存方面没有实用的上限;MySQL是这样的
  • 大部分时间用于在MySQL和PHP之间传输数据
除非我误解了这项任务,否则你在这方面做得太辛苦了。只需将其交给MySQL即可完成所有工作。没有数组,没有分块,只有一个SQL:

INSERT INTO table2
          (Name, Company, ProductName, Status, cron_modified_date, count)
    SELECT Name, Company, ProductName, Status, created_date, 1
        FROM table1
    ON DUPLICATE KEY UPDATE
        count = count + 1
        cron_modified_date = created_date;
请注意,在某些更新中可能需要使用伪函数
VALUES()


这避免了将所有(甚至5000)行提取到PHP中,这可能是内存问题的根源。PHP中的一个简单变量大约需要40个字节。MySQL的设计目的是在不破坏RAM的情况下处理任意数量的行。

@AtaurRahman:是的,你是对的,但他有关于重复密钥更新的
声明。@SalimIbrogimov,这有什么关系?正如我所知,
在重复密钥更新上
可以很好地工作。@AtaurRahman:
在重复密钥更新计数=(计数+1),cron_modified_date=“$getRow['created_date']。”;
为什么要使用gc_disable();?是否使用PDO或MySQLi作为数据库层?是的,我正在使用PDO,在调用gc_collect_cycles()之前;,我正在禁用GC。
INSERT…SELECT
不是这种情况的答案。“我们在两台服务器中有两个类似的表”@RaymondNijland:是的,同意。不知道我是如何丢失它的。我该怎么办?删除答案?编辑问题您的答案中有3个选项?