2012-11-24 20 views
0

在我的symfony2命令中,我正在運行一個腳本,它將數十萬個URL(以字符串形式)插入到文檔中。在Symfony2中mongodb批量插入的最佳做法

這裏是我正在使用的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中插入數百萬行,而讀/寫速度變慢的最佳做法是什麼?

回答

1

爲了在教條中進行批量插入,您需要將循環外部的刷新移動到外部。考慮下面的情況,當foreach被完成時,你會堅持在foreach中flush。唯一的問題是,直到沖洗之後,您才能查詢批次中插入的任何數據。

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

另一種方法是做一個推,pushall或addto設置。 需要考慮的一個問題是你需要在php中使用stdClass來添加一個對象。 我覺得這是更新子文檔最快捷的方法。 例如:

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