在我的symfony2命令中,我正在运行一个脚本,该脚本将数十万个url(作为字符串)插入到文档中。
以下是我正在使用的两个文档的基本结构。在程序运行之前,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中插入数百万行的最佳做法是什么?
我会避免使用Symfony的文档管理器,而是直接使用batchInsert()函数。文档中对此进行了描述,网址为http://php.net/manual/en/mongocollection.batchinsert.php在我看来,Doctrine的ODM实际上在伤害你。
为了在原则中进行批量插入,您需要将flush移到循环之外。考虑下面的场景,您将坚持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();
另一种选择是进行推送、pushall或addto设置。需要考虑的一个问题是,您需要在php中使用stdClass来添加对象。我发现这是更新子文档的最快方法。例如:
$dm->createQueryBuilder('My:Bundle:ParentDocument')
->update()
->field('subDocument')->push( (object) array('url'=> $url) )
->field('id')->equals( $parentDocumentId )
->getQuery()
->execute();