【问题标题】:PHP Getting out of memoryPHP 内存不足
【发布时间】:2016-04-22 06:40:52
【问题描述】:

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

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 函数或 map 函数内的循环有问题。我什至尝试将内存设置和时间限制设置设置为无限制,但无济于事。可能是我需要使用引用变量或其他东西,但我不确定如何。

可以对以上代码进行优化以减少内存使用吗?

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

谁能告诉我在这里做错了什么或者为什么整个内存都被消耗掉了?

PS:我在具有 4GB 内存(Windows 8)的本地开发机器上执行此操作。 PHP版本:5.6.16

【问题讨论】:

  • 您允许 PHP 使用多少内存? (这可以在php.ini 中更改)。对不起,忽略这个:p 我没有看到你的代码的开头。
  • @PXgamer:在第一行可以看到无限ini_set('memory_limit', '-1'); 我还尝试设置为其他值,例如ini_set('memory_limit', '1024MB'); 或更大,但没有运气。
  • 你试过减小块大小吗?
  • @Sarcoma:是的,尝试将 at 设置为较小的值,但奇怪的是,它在大约 80000 记录时再次出现内存不足
  • 有什么“32 位”应用程序吗? 1.7G 是此类应用无法胜任的地方。

标签: php mysql laravel memory-management


【解决方案1】:

是的,您可以更改“memory_limit”。但这仅适用于今天,而不是明天,那时您将需要更多内存。

A 计划:

相反,编写更多代码...一次将数据分成 1000 行。构建一个包含所有行的 INSERT 语句。单独在事务中执行。

B计划:

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

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

【讨论】:

  • 我已经在使用 chunk 的 eloquent 方法对数据进行分块,可以在代码中看到,不能分块超过 500 以避免参数过多错误并且 csv 不是选项,需要做通过代码。代码中存在内存泄漏的问题,可能需要识别。谢谢
【解决方案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);
}

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

【讨论】:

  • 感谢生成器+步骤。我修改了一些代码以将数据插入块中,但感谢您的想法。
【解决方案3】:

当您获取 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】:

    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'];
    }
    

    【讨论】:

      【解决方案5】:

      一些建议。

      • 在每个循环中实例化一个新的$object 对象。取决于MySqlModel 的实际结构和项目的数量,它肯定会使用大量内存(也因为 GC 尚未工作,请参阅第二个建议)。在每个循环结束时将其设置为 NULL,即

      $object = NULL;

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

      【讨论】:

        【解决方案6】:

        map 将返回您的集合的新实例。 GC 会为时已晚。

        尝试替换

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

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

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

        GC 现在应该收集它,但您可以在插入后添加unset($newchunk); 以确保。代码倒数第二行中的unset($object); 也不会受到影响。

        【讨论】:

        • 你试过取消设置吗?我还有另一个想法:你可以尝试用例如替换foreach 中的$key-variable $key1? foreach ($attributes as $key1 => $attribute) { if ($attribute === null) { $attributes[$key1] = ''; } }?不知道会不会导致内存泄漏,但感觉很可疑,可能会堆积。
        猜你喜欢
        • 1970-01-01
        • 2018-07-21
        • 2015-08-13
        • 1970-01-01
        • 1970-01-01
        • 2020-01-08
        • 2012-10-25
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多