PHP内存不足

时间:2016-04-22 06:40:52

标签: php mysql laravel memory-management

我正在尝试将postgres数据库中的数据插入到mysql数据库中。我需要导入约100000条记录。但是Iam总是出现内存问题。

Out of memory (allocated 1705508864) (tried to allocate 222764 bytes)

我使用Laravel 5来执行此操作,这里是代码:

// to avoid memory limit or time out issue
ini_set('memory_limit', '-1');
ini_set('max_input_time', '-1');
ini_set('max_execution_time', '0');
set_time_limit(0);

// this speeds up things a bit
DB::disableQueryLog();

$importableModels = [
    // array of table names
];

$failedChunks = 0;

foreach ($importableModels as $postGresModel => $mysqlModel) {

    $total = $postGresModel::count();
    $chunkSize = getChunkSize($total);

    // customize chunk size in case of certain tables to avoid too many place holders error
    if ($postGresModel === 'ApplicationFormsPostgres') {
        $chunkSize = 300;
    }

    $class = 'App\\Models\\' . $mysqlModel;
    $object = new $class;

    // trucate prev data //
    Eloquent::unguard();
    DB::statement('SET FOREIGN_KEY_CHECKS=0;');
    $object->truncate();
    DB::statement('SET FOREIGN_KEY_CHECKS=1;');
    Eloquent::reguard();

    $postGresModel::chunk($chunkSize, function ($chunk) use ($postGresModel, $mysqlModel, $failedChunks, $object) {

        // make any adjustments
        $fixedChunk = $chunk->map(function ($item, $key) use ($postGresModel) {

            $appendableAttributes = $postGresModel::APPEND_FIELDS;
            $attributes = $item->getAttributes();

            // replace null/no values with empty string
            foreach ($attributes as $key => $attribute) {
                if ($attribute === null) {
                    $attributes[$key] = '';
                }
            }

            // add customized attributes and values
            foreach ($appendableAttributes as $appendField) {
                if ($appendField === 'ssn') {
                    $value = $attributes['number'];
                    $attributes[$appendField] = substr($value, 0, 4);
                } else {
                    $attributes[$appendField] = '';
                }

            }

            return $attributes;
        });

        // insert chunk of data in db now
        if (!$object->insert($fixedChunk->toArray())) {
            $failedChunks++;
        }

    });    
}

在此之前插入约80000行时会出现内存问题。

我怀疑地图函数中的集合map函数或循环有问题。我甚至尝试将内存设置和时间限制设置设置为无限制,但无济于事。可能是我需要使用引用变量或其他东西,但我不确定如何。

可以在上面的代码中进行任何优化以减少内存使用吗?

或者如何通过代码有效地将大型数据从大型PostgreSQL数据库导入MySQL?

任何人都可以告诉我这里做错了什么,或者为什么整个记忆都消耗殆尽了?

PS:我在本地开发机器上做这个,它有4GB内存(Windows 8)。 PHP版本:5.6.16

6 个答案:

答案 0 :(得分:4)

是的,您可以更改'memory_limit'。但这只能在今天,而不是明天,当你需要更多的记忆时。

计划A:

相反,再写一点代码......将数据分成几行,比如一次1000行。构建一个包含其中所有行的INSERT语句。单独在事务中执行它。

B计划:

构建所有行的CSV文件,然后使用LOAD DATA INFILE进行批量插入。

在任一计划中,避免一次将所有行加载到RAM中。 PHP中的标量和数组有一个很多的开销。

答案 1 :(得分:2)

当然,你的内存泄漏了。我想在$chunk->map()$object->insert($fixedChunk->toArray())范围内。我们只能猜测,因为实施是隐藏的。

但是,我会尽可能地使用generators。代码可能如下所示:

function getAllItems() {
  $step = 2000;

  for ($offset = 0 ;; $offset += $step) {
    $q = "SELECT * FROM items_table LIMIT $offset, $step";

    if (! $items = Db::fetchAll($q)) {
      break;
    }

    foreach ($items as $i) {
      yield $i;
    }
  }
}

foreach (getAllItems() as $item) {
  import_item($item);
}

我敢说,使用生成器,您几乎可以将任何数据量从一个数据库导入另一个数据库。

答案 2 :(得分:1)

1.-尝试评论数据处理逻辑的内容,以检查内存泄漏是否在此代码中:

$postGresModel::chunk($chunkSize, function ($chunk) use ($postGresModel, $mysqlModel, $failedChunks, $object) {

        // make any adjustments
        $fixedChunk = $chunk->map(function ($item, $key) use ($postGresModel) {

              ///Nothing to do 
       }
}

2.-如果你得到同样的错误,当试图从查询结果中转储所有行时,mysql驱动程序(PDO?)可能会产生内存泄漏,缓冲内存中的所有行。

PostgreSQL unbuffered queries and PHP (cursors)中,您可以使用游标更改hoy postgreSql获取行的行为:

$curSql = "DECLARE cursor1 CURSOR FOR SELECT * FROM big_table";
$con = new PDO("pgsql:host=dbhost dbname=database", "user", "pass");
$con->beginTransaction(); // cursors require a transaction.
$stmt = $con->prepare($curSql);
$stmt->execute();

$innerStatement = $con->prepare("FETCH 1 FROM cursor1");

while($innerStatement->execute() && $row = $innerStatement->fetch(PDO::FETCH_ASSOC)) {
    echo $row['field'];
}

答案 3 :(得分:1)

当你获取PostgreSQL数据时,尝试LIMIT返回的内容(http://www.postgresql.org/docs/8.1/static/queries-limit.html)的大小合理然后迭代。

比如说你一次拿走20000行,你会做' SELECT .. BLAH .. LIMIT 20000 OFFSET 0',然后下一次迭代将是' SELECT .. BLAH .. LIMIT 20000 OFFSET 20000',(OFFSET为20000 *你的循环计数器)。

处理这些批次,直到您没有剩余行。

答案 4 :(得分:0)

一些建议。

  • 您在每个循环中实例化一个新的$object对象。根据{{​​1}}的实际结构和项目数量,它肯定会使用大量内存(也因为GC尚未运行,请参阅第二个建议)。在每个循环结束时将其设置为NULL,即

MySqlModel

  • 如果执行时间不是问题,请在每个循环之间插入一点延迟。这允许PHP垃圾收集器完成一些工作并释放未使用的资源。

答案 5 :(得分:0)

map将返回您的收藏集的新实例。 GC会过早清理它。

尝试替换

$chunk = $chunk->map(function... 

$newchunk = $chunk->map(function... 

当然在插入时使用新块$object->insert($newchunk->toArray())。您也可以使用transform代替map

GC现在应该收集它,但您可以在插入后添加unset($newchunk);以确保。 unset($object);也不会受到影响。

相关问题