【问题标题】:Memory usage goes wild with Doctrine bulk insertDoctrine 批量插入使内存使用变得疯狂
【发布时间】:2016-01-30 08:31:04
【问题描述】:

我正在尝试使用 Doctrine2 和 Symfony2 夹具包在 MySQL 数据库中插入大量数据(超过 30 000 行)。

我看了the right way to do it。我看到了很多关于内存泄漏和 Doctrine 的问题,但没有让我满意的答案。它通常带有 Doctrine clear() 函数。

所以,我做了各种不同的形状:

while (($data = getData()) {
    $iteration++;

    $obj = new EntityObject();
    $obj->setName('henry');
    // Fill object...

    $manager->persist($obj);

    if ($iteration % 500 == 0) {
        $manager->flush();
        $manager->clear();

        // Also tried some sort of:
        // $manager->clear($obj);   
        // $manager->detach($obj);
        // gc_collect_cycles();
    }
}

flush() 之后(我很确定),PHP 内存仍然疯狂。实际上,每次刷新实体时,内存都会根据批次大小和实体增加一定量,直到达到致命的Allowed Memory size exhausted错误。对于一个非常非常小的实体,它可以工作,但内存消耗增加太多:几 MB,而应该是 KB。

clear()detach() 或者调用 GC 似乎根本没有效果。它只会清除一些 KB。

我的方法有缺陷吗?我在某个地方错过了什么吗?是bug吗?

更多信息

  • 没有flush(),内存几乎不会移动;
  • 降低批次不会改变结果;
  • 数据来自需要清理的 CSV;

编辑(部分解决方案)

@qooplmao 带来了显着减少内存消耗的解决方案,禁用学说 sql logger:$manager->getConnection()->getConfiguration()->setSQLLogger(null);

但是,它仍然异常高并且还在增加。

【问题讨论】:

  • 您是否尝试降低批量大小(500)?它会更慢,但内存占用更少
  • 是的。我尝试降低它(100、20、1)并增加它(1000、2000、5000),没有变化。
  • 这里只是好奇,getData() 返回什么?它从哪里获取信息?多少钱?与文档相比,循环的选择很有趣,我很喜欢
  • 你是在开发中做这个吗?如果是这样,您最好禁用 SQL 记录器 ($manager->getConnection()->getConfiguration()->setSQLLogger(null))。
  • @Gui-Don,你能试试这个吗? coderwall.com/p/awzjhw/…

标签: php symfony memory doctrine-orm


【解决方案1】:

按照@Axalix 的建议,我使用this resource 解决了我的问题。

这就是我修改代码的方式:

// IMPORTANT - Disable the Doctrine SQL Logger
$manager->getConnection()->getConfiguration()->setSQLLogger(null);

// SUGGESION - make getData as a generator (using yield) to to save more memory.
while ($data = getData()) {
  $iteration++;

  $obj = new EntityObject();
  $obj->setName('henry');
  // Fill object...

  $manager->persist($obj);

  // IMPORTANT - Temporary store entities (of course, must be defined first outside of the loop)
  $tempObjets[] = $obj;

  if ($iteration % 500 == 0) {
    $manager->flush();

    // IMPORTANT - clean entities
    foreach($tempObjets as $tempObject) {
      $manager->detach($tempObject);
    }

    $tempObjets = null;
    gc_enable();
    gc_collect_cycles();
  }
}

// Do not forget the last flush
$manager->flush();

最后但同样重要的是,当我将此脚本与 Symfony 数据装置一起使用时,在命令中添加 --no-debug 参数也非常重要。那么内存消耗就稳定了。

【讨论】:

  • 对 $tempObjects 的赋值的评论。当您保存批次时,您将对象从 entityManager 中分离出来,但您没有清除 $tempObjects。这意味着如果您保存 2000 个项目,您将分离 500 个对象,然后是 1000 个对象,然后是 1500 个对象。保存批处理时清除 $tempObjects 是否没有意义。
  • @JeremyQuinton 你是对的,他需要在分离后重置 $tempObjects。
  • 如果我们要插入50000个对象,但执行这段代码后只插入了48000个,我们如何识别未插入的对象以及如何获得插入对象的确切数量?
  • 确认了这个解决方案。将需要数小时的导入时间缩短至几分钟。
  • 单独 --no-debug 为我做了!
【解决方案2】:

我发现 Doctrine 在执行期间记录了所有 SQL。我建议用下面的代码禁用它,它确实可以节省内存:

use Doctrine\ORM\EntityManagerInterface;


public function __construct(EntityManagerInterface $entity_manager)
{
    $em_connection = $entity_manager->getConnection();
    $em_connection->getConfiguration()->setSQLLogger(null);
}

【讨论】:

    【解决方案3】:

    我的建议是放弃批量插入的 Doctrine 方法。我真的很喜欢 Doctrine,但我只是讨厌批量插入的这种东西。

    MySQL 有一个很棒的东西叫做LOAD DATA。我宁愿使用它,或者即使我必须先清理我的 csv 并在之后执行 LOAD。

    如果您需要更改值,我会将 csv 读取到数组 $csvData = array_map("str_getcsv", file($csv));。更改阵列上所需的任何内容并将其保存到行中。之后,使用新的 .csv 文件与 MySQL 一起加载。

    为了支持我关于为什么我不会将 Doctrine 用于顶部描述的 here 的主张。

    【讨论】:

    • 嗯...有点可惜,因为据我了解,有人说它不是批量插入的最佳工具,主要是出于性能角度。然而,就我而言,我并不真正关心性能(它是数千行,而不是数百万行!)。它应该可以正常工作。话虽如此,谢谢您的帮助,我会尝试的。但是,我如何方便地与 LOAD DATA 建立关系?我是否必须为已建立关系的每个表创建一个 CSV?
    • 是的,我知道性能对你来说可能不是很重要,但如果你能以最好的性能做到这一点,那么做它会很酷。您不能对多个表执行此操作,但您可以创建一个临时表,LOAD INTO 临时表,然后 INSERT INTO 表 SELECT FROM 临时表。这听起来很棘手,但实际上这样做更好。
    • 你可以使用dbal连接对象(甚至pdo连接对象)来准备和执行sql插入语句,而不是下拉到mysql loadddata。这也将允许您插入关系。我使用这种方法来处理 5K 左右的相当复杂的实体。 30K可能仍然是一个延伸。 php 本身并不是真正为大批量作业而设计的。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-03-25
    • 2014-03-08
    • 2020-07-30
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多