Doctrine2协会在地狱中持续缓慢

时间:2015-09-03 07:29:53

标签: php symfony doctrine-orm

我在创建主要实体while的地方有一个Order循环,这个实体有很多字段和关联但是持续快速,非常快。

但它作为一个oneToMany单向(manyToMany)关联,永远需要...这种关联很小,只有两个字段,所以我不知道为什么需要这么长时间。

我有一个$items数组,正好是17项。在while循环中,foreach Order我在assoc中添加了17个项目。

while($i < 53000)
{
    // ... 
    foreach($item as $k => $v)
    {
        $item = new Item();
        $item->setName($v['name']);
        $item->setValue($order[$v['column']]);

        $order->addItem($item);
    }

    // ...

    $em->persist($order);

    $i++;
    if($i % 200 == 0) // I've tried multiples values in here but everything is slow.
    {
        $em->flush();
        $em->clear(); // Commenting this, doesn't speed up things neither.
    }
}

当然我知道每OrderItem创建了Item,但由于Item对象太小,我觉得它不需要花那么长时间......

使用forEach manyToMany: items: targetEntity: AppBundle\Entity\Item cascade: ["all"] joinTable: name: order_has_item joinColumns: product_id: referencedColumnName: id inverseJoinColumns: item_id: referencedColumnName: id ,结束脚本大约需要35分钟。如果我删除它,脚本只需要4分钟就可以运行,这很荒谬......

关系映射如下

Item

它是单向的,因此不会创建Applications does not run in background的映射。

1 个答案:

答案 0 :(得分:3)

你正在生成53000 Order个,其中每个都有(如你所说)大约17 Item s。这是生成的954000个数据库条目。

您每隔200 Order持续一次,因此每次调用$em->persist()都会创建3600个实体对象和数据库条目。

至少有两次性能点击:

  1. Doctrine将生成3600个具有所有铃声和口哨声的实体。
  2. 每个插入都会更新受影响表的数据库索引。
  3. 此外,请不要忘记,如果您在dev环境中执行此操作,Symfony将记录大量内容(请参阅app/logs/dev.log)并具有多个调试功能。< / p>

    那么,你如何将近百万个条目推入你的数据库?不幸的是,在真正的批量操作中,Doctrine非常糟糕。使用本机SQL查询通常更好(但是,牺牲了可移植性......但无论如何,谁在乎):

    $em->getConnection()->executeUpdate(/*INSERT etc. …*/);
    

    当插入彼此相关的对象时,这有点困难,因为我们必须确保一致性:

    try
    {
        $conn = $em->getConnection();
        $conn->beginTransaction();
    
        foreach ($orders as $order)
        {
            $conn->executeUpdate(/*INSERT the order …*/);
            $orderId = $conn->lastInsertId();
    
            foreach ($order->items as $item)
            {
                $item->orderId = $orderId;
                $conn->executeUpdate(/*INSERT the item …*/);
            }
        }
    
        $conn->commit();
    }
    catch(\Exception $e)
    {
        $conn->rollback();
        throw $e;
    }
    
    // NOTE: the try/catch wraps *all* orders, so if one order fails *no* 
    // orders will be saved. Alternatively, put the try/catch into the loop
    

    (为了便于阅读,我保持代码简短,你会得到这个想法。没有经过测试,只是从我的头脑中开始。)

    查看Doctrine documentation以了解有关原生查询的更多信息。

    顺便说一下,在您的示例中,如果您只在($i % 200 == 0)时写入数据库,那么您将丢失($i % 200)最后一项。你需要写一些类似的东西:

    $amount = 53000;
    
    while($i < $amount)
    {
        // do your thing
    
        $i++;
    
        if($i % 200 === 0 || $i >= $amount)
        {
            // flush/clear
        }
    }