【问题标题】:Doctrine2 - Multiple insert in one shotDoctrine2 - 一次多次插入
【发布时间】:2013-09-10 08:41:51
【问题描述】:

我是 Doctrine 的新手,对我来说还有一些模糊的地方。在这种情况下,我使用循环和实体管理器在数据库中插入新记录。它工作正常,但我注意到 Doctrine 按实体进行插入查询,这可能会变得非常庞大。

使用 Doctrine2 和 Symfony 2.3,我想知道我们如何设置它,这样它就可以只使用其中的所有值进行 1 个插入查询(我们当然只讨论 1 个实体)。

我的意思是改变这个:

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();

【问题讨论】:

  • 出于什么原因,您会将这些查询合并为一个查询?
  • 我在考虑提高性能。这只是一个示例,在实践中最有可能插入大约 20 个实体。因此,仅建立连接将比 n 连接快得多。编辑:我找到了this answer 关于这个话题。
  • 我可能会警告您,原则会在您执行的每个插入操作(管理状态等)上增加相当多的开销,因此对于非常大的插入,我会选择 DBAL 查询而不是 ORM 关系. // 只是我的 2 美分

标签: php mysql symfony doctrine-orm


【解决方案1】:

根据this answer,Doctrine2 不允许您将多个 INSERT 语句合并为一个:

有些人似乎想知道为什么 Doctrine 不使用 多插入(插入(...)值(...),(...),(...),...

首先,这个语法只支持mysql和更新的 postgresql 版本。其次,没有简单的方法来掌握所有 使用时在这样的多插入中生成的标识符 AUTO_INCREMENT 或 SERIAL 并且 ORM 需要身份标识符 对象的管理。最后,插入性能很少是 ORM 的瓶颈。普通插入速度足够快 大多数情况下,如果您真的想进行快速批量插入,那么 无论如何,多插入不是最好的方法,即 Postgres COPY 或 Mysql LOAD DATA INFILE 的速度要快几个数量级。

这些是不值得努力实施的原因 在 mysql 和 postgresql 上执行多插入的抽象 ORM。

您可以在此处阅读有关 Doctrine2 批处理的更多信息: http://www.doctrine-project.org/blog/doctrine2-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 是不可能的。
  • +1 刷新剩余的对象...文档应该真正包含这一点,就好像您的批量大小小于结果数一样没有任何反应
  • 请注意$em->clear() 可能会对您在函数中未直接处理的其他实体产生错误影响。例如,您可以通过与您的产品类别的用户相关的一些异常...因此,仅清除具有您想要的类型的实体会更方便。在这种情况下,它将是 $em->clear('Product')
【解决方案2】:

你可以试试这个 fork https://github.com/stas29a/doctrine2。它完全实现了您想要的。我在 MySQL 中对其进行了测试,它运行良好,并且比批处理快 5 倍。这个 fork 获取第一个插入的 id 并在 php 中递增它以获取其他 id。它适用于大多数情况,但并非全部适用。所以你需要了解你在使用这个 fork 时在做什么。

【讨论】:

    【解决方案3】:

    您可以使用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
        );
    }
    

    【讨论】:

      【解决方案4】:

      我尚未对其进行测试,但似乎可以通过集合来做到这一点。

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

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

      【讨论】:

      • 我会尝试的,但是如果每次我想插入多个值时都必须指定表的名称,那么解决方案对我来说有点过于“复杂”了。
      猜你喜欢
      • 2015-05-02
      • 2015-04-14
      • 1970-01-01
      • 2013-11-23
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-08-13
      • 1970-01-01
      相关资源
      最近更新 更多