Symfony2中mongodb批量插入的最佳实践

时间:2012-11-24 08:00:43

标签: mongodb symfony doctrine doctrine-orm symfony-2.1

在我的symfony2命令中,我正在运行一个脚本,该脚本将数十万个URL(作为字符串)插入到文档中。

以下是我正在使用的2份文件的基本结构。在程序运行之前,mongodb中已有数千个ParentDocuments,但是没有ChildDocuments:

ParentDocument:
    $id:id
    $subDocument:OneToManyReference(ChildDocument)
    $etc:everythingelse

ChildDocument:
    $id:id
    $url:string
    $parentDocument:ManyToOneReference(ParentDocument)

我的命令代码:

$dm = $this->getContainer()->get('doctrine_mongodb.odm.document_manager');
$parentDocuments = $dm->repository('My:Bundle:ParentDocument')->findAll();
while ($parentDocument = $parentDocuments->getNext()) {
    //Returns an array of hundreds of thousands urls
    $urls = $this->somehowFetchUrlsRelatedToTheParentDocument($parentDocument);
    foreach ($urls as $url) {
        $subDocument = new SubDocument();
        $subDocument->setUrl($url);
        $subDocument->setParentDocument($parentDocument);
        $dm->persist($subDocument);
    }
    $dm->flush();
}

当我运行这个简单的命令时,写入速度首先非常快。但是,在插入数百万行的情况下,写入速度变得非常慢。在命令运行10分钟后,速度低至每秒1次,使代码极无效。

我首次尝试修复此问题是在使用$dm->clear();刷新后立即清除文档管理器 但这意味着文档管理器将失去对当前ParentDocument的跟踪。所以我的解决方案是:

$dm = $this->getContainer()->get('doctrine_mongodb.odm.document_manager');
$parentDocumentCursors = $dm->repository('My:Bundle:ParentDocument')->findAll();
$parentDocuments = array();
while ($parentDocument = $parentDocumentCursors->getNext()) {
    array_push($parentDocuments, $parentDocument);
}
$dm->clear();
unset($dm);
$dm = $this->getContainer()->get('doctrine_mongodb.odm.document_manager');
foreach ($parentDocuments as $parentDocument) {
    $urls = $this->somehowFetchUrlsRelatedToTheParentDocument($parentDocument);
    foreach ($urls as $url) {
        $subDocument = new SubDocument();
        $subDocument->setUrl($url);
        $subDocument->setParentDocument($parentDocument);
        $dm->persist($subDocument);
    }
    $dm->flush();
    $dm->clear();
}

这解决了这个问题。在整个程序执行过程中,写入速度一直很快,并且可以插入数百万行而不会出现逐渐延迟。

然而,这感觉就像一个糟糕的做法和一个快速修复黑客。在没有读/写速度变慢的情况下使用文档管理器在Symfony2中插入数百万行的最佳做法是什么?

2 个答案:

答案 0 :(得分:3)

我会避免使用Symfony的文档管理器并直接使用batchInsert()函数。这在http://php.net/manual/en/mongocollection.batchinsert.php的文档中有所描述。感觉像Doctrine的ODM实际上在这里伤害了你。

答案 1 :(得分:1)

为了在学说中进行批量插入,您需要将齐次移动到循环之外。考虑下面的场景,您将在foreach中保留,然后在foreach完成时刷新。您唯一的问题是,在刷新之前,您将无法查询批处理中插入的任何数据。

$dm = $this->getContainer()->get('doctrine_mongodb.odm.document_manager');
foreach ($parentDocuments as $parentDocument) {
    $urls = $this->somehowFetchUrlsRelatedToTheParentDocument($parentDocument);
    foreach ($urls as $url) {
        $subDocument = new SubDocument();
        $subDocument->setUrl($url);
        $subDocument->setParentDocument($parentDocument);
        $dm->persist($subDocument);
    }    
 }
$dm->flush();
$dm->clear();

另一个选择是执行push,pushall或addto设置。 需要考虑的一个问题是你需要在php中使用stdClass来添加一个对象。 我发现这是更新子文档的最快方法。 例如:

$dm->createQueryBuilder('My:Bundle:ParentDocument')
    ->update()
    ->field('subDocument')->push( (object) array('url'=> $url) )
    ->field('id')->equals( $parentDocumentId )
    ->getQuery()
    ->execute();