Doctrine2 - 一次性批量插入多条记录

34

我对Doctrine还比较陌生,有些地方不太清楚。在这种情况下,我正在使用循环和实体管理器向数据库插入新记录。虽然它能正常工作,但我注意到Doctrine会根据实体进行一次插入查询,这可能会非常庞大。

我想知道如何使用Doctrine2和Symfony 2.3来设置,以便只生成一个包含所有值的插入查询(当然,我们只考虑一种实体)。

我的意思是将这个东西改成:

INSERT INTO dummy_table VALUES (x1, y1)    
INSERT INTO dummy_table VALUES (x2, y2)

进入

INSERT INTO dummy_table VALUES (x1, y1), (x2, y2)

这是我的代码:

$em = $this->container->get('doctrine')->getManager();

foreach($items as $item){
    $newItem = new Product($item['datas']);
    $em->persist($newItem);
}

$em->flush();

你会将这些查询合并为一个的原因是什么? - Touki
1
我在考虑性能改进。这只是一个例子,在实践中可能需要插入大约20个实体。因此,仅建立一次连接比建立n个连接要快得多。编辑:我找到了关于这个话题的这个答案 - Molkobain
我可能要警告你,Doctrine 在每次插入时都会增加相当多的开销(管理状态等),因此对于非常大的插入,我会选择使用 DBAL 查询而不是 ORM 关系。// 仅代表个人意见 - Sebastiaan Hilbers
5个回答

57
根据这个答案,Doctrine2不允许将多个INSERT语句合并为一个:
一些人似乎想知道为什么Doctrine不使用多次插入(insert into (...) values (...), (...), (...),...)
首先,此语法仅在mysql和更新的postgresql版本上受支持。其次,在使用AUTO_INCREMENT或SERIAL时,很难获取此类多次插入中生成的所有标识符,并且ORM需要标识符来管理对象的标识。最后,插入性能很少是ORM的瓶颈。对于大多数情况而言,普通插入足够快,如果您真的想进行快速批量插入,那么多次插入不是最佳选择,即Postgres COPY或Mysql LOAD DATA INFILE比多次插入快几个数量级。
这些是不值得在ORM中实现在mysql和postgresql上执行多次插入的抽象的原因。
您可以在此处阅读有关Doctrine2批处理的更多信息: https://www.doctrine-project.org/projects/doctrine-orm/en/latest/reference/batch-processing.html 您可以切换到DBAL,也可以通过在插入一定量之后刷新实体管理器来分批处理数据。
$batchSize = 20;

foreach ($items as $i => $item) {
     $product = new Product($item['datas']);

     $em->persist($product);

     // flush everything to the database every 20 inserts
     if (($i % $batchSize) == 0) {
         $em->flush();
         $em->clear();
    }
}

// flush the remaining objects
$em->flush();
$em->clear();

谢谢,现在我知道通过Doctrine是不可能实现的。 - Molkobain
3
请问需要翻译成哪种语言呢? - Carlton
4
注意 $em->clear() 可能会对你的函数中没有直接处理的其他实体产生错误影响。例如,与用户、类别或产品相关的异常可能会出现......因此,最好只清除您想要的类型的实体。在这种情况下,它将是 $em->clear('Product') - FlameStorm

2
你可以使用DriverConnection接口的executeUpdate($query, array $params = array(), array $types = array())方法来执行此操作。但是绑定多个参数有点棘手。
数据:
$postMetaData = [
    [
        'post_id' => $product->getId(),
        'meta_key' => '_visibility',
        'meta_value' => 'visible',
    ],
    [
        'post_id' => $product->getId(),
        'meta_key' => '_stock_status',
        'meta_value' => $insert['in_stock'] ? 'instock' : 'outofstock',
    ]
];

批量更新方法:
public function updateOrCreateBulk($posts, \Doctrine\DBAL\Connection $connection)
{

    $placeholders = [];
    $values = [];
    $types = [];

    foreach ($posts as $columnName => $value) {
        $placeholders[] = '(?)';
        $values[] = array_values($value);
        $types[] = \Doctrine\DBAL\Connection::PARAM_INT_ARRAY;
    }

    return $connection->executeUpdate(
        'INSERT INTO `wp_postmeta` (`post_id`, `meta_key`, `meta_value`)  VALUES ' . implode(', ', $placeholders) . ' ON DUPLICATE KEY UPDATE `meta_value` = VALUES(`meta_value`)',
        $values,
        $types
    );
}

2
您可以尝试这个分支 https://github.com/stas29a/doctrine2,它实现了您想要的功能。我在MySQL中测试过它,它的表现很好,并且比批量处理快5倍。这个分支会获取第一个插入的id并在php中递增来获取其他的id。这对大多数情况都有效,但不是所有情况都适用。因此在使用这个分支时,需要理解您在做什么。

0

谢谢,现在我知道用Doctrine不可能实现了。-@Molkobain

你可以使用一次性的操作:

$em->merge($testCustomer);
然后执行persist flush即可。

这样就可以完美地工作了。


-10

我还没有测试过,但似乎可以使用集合来实现这个。

$collection = new Doctrine_Collection('tablename');
$collection->add($record1);
$collection->add($record2);
$collection->add($record3);
$collection->add($record4);
$collection->save();

当然你应该在循环中添加。


我会尝试,但如果每次我想插入多个值都必须指定表名,那对我来说就变得有点太“复杂”了。 - Molkobain

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接