Symfony:Doctrine数据夹具:如何处理大型CSV文件?

11

我正在尝试使用Doctrine数据夹具从“大型”CSV文件(3Mo / 37000行/ 7列)向MySQL数据库插入数据。

这个过程非常缓慢,目前我还没有成功(也许我需要再等待一段时间)。

我认为Doctrine数据夹具不适合管理如此大量的数据?也许解决方案应该是直接将我的csv导入数据库?

有什么建议吗?

以下是代码:

<?php

namespace FBN\GuideBundle\DataFixtures\ORM;

use Doctrine\Common\DataFixtures\AbstractFixture;
use Doctrine\Common\DataFixtures\OrderedFixtureInterface;
use Doctrine\Common\Persistence\ObjectManager;
use FBN\GuideBundle\Entity\CoordinatesFRCity as CoordFRCity;

class CoordinatesFRCity extends AbstractFixture implements OrderedFixtureInterface
{
    public function load(ObjectManager $manager)
    {
        $csv = fopen(dirname(__FILE__).'/Resources/Coordinates/CoordinatesFRCity.csv', 'r');

        $i = 0;

        while (!feof($csv)) {
            $line = fgetcsv($csv);

            $coordinatesfrcity[$i] = new CoordFRCity();
            $coordinatesfrcity[$i]->setAreaPre2016($line[0]);
            $coordinatesfrcity[$i]->setAreaPost2016($line[1]);
            $coordinatesfrcity[$i]->setDeptNum($line[2]);
            $coordinatesfrcity[$i]->setDeptName($line[3]);
            $coordinatesfrcity[$i]->setdistrict($line[4]);
            $coordinatesfrcity[$i]->setpostCode($line[5]);
            $coordinatesfrcity[$i]->setCity($line[6]);

            $manager->persist($coordinatesfrcity[$i]);

            $this->addReference('coordinatesfrcity-'.$i, $coordinatesfrcity[$i]);


            $i = $i + 1;
        }

        fclose($csv);

        $manager->flush();
    }

    public function getOrder()
    {
        return 1;
    }
}
3个回答

15

创建大批量导入时应遵循的两个规则:

  • 禁用SQL日志记录:($manager->getConnection()->getConfiguration()->setSQLLogger(null);)以避免内存损失过大。

  • 频繁执行Flush和Clear,而不仅仅在最后一次执行。我建议您在循环内添加if ($i % 25 == 0) { $manager->flush(); $manager->clear() },每执行25个INSERT就进行Flush操作。

编辑: 我忘记的最后一件事:在不再需要它们的情况下,请不要将实体保留在变量中。在这里,在您的循环中,您只需要处理当前实体,因此不要将先前的实体存储在$coordinatesfrcity数组中。如果您一直这样做,可能会导致内存溢出。


谢谢。我曾尝试在每次插入时刷新,但我认为这是一个太重要的频率。我会尝试您的建议。不好意思,什么是SQL日志记录? - Cruz
1
Doctrine自带一个日志记录系统,可记录已执行的SQL查询的轨迹。在此情况下,您不需要它,而且它只会浪费内存空间。 - Terenoth
好的,我明白什么是SQL日志记录。你说得对,这个表来自另一段代码,在这里并不必要。 - Cruz
1
它运行完美。在我的Linux PC(LAMPP)上加载完整需要大约一分钟的时间。 - Cruz
小错误:应该是==而不是=,在代码中的这一行:if ($i % 25 == 0) - user1915746
2
真是令人难以置信。我正在导入一份有14列和453000行(大小为200Mo)的CSV文件。脚本花了48小时才导入完所有行。通过这次优化,现在只需要10分钟就可以完成。太棒了。非常感谢! - Gangai Johann

1

文档中有一个很好的例子:http://doctrine-orm.readthedocs.org/projects/doctrine-orm/en/latest/reference/batch-processing.html

使用模运算(x % y)表达式来实现批处理,此示例将每次插入20条。您可以根据服务器进行优化。

$batchSize = 20;
for ($i = 1; $i <= 10000; ++$i) {
    $user = new CmsUser;
    $user->setStatus('user');
    $user->setUsername('user' . $i);
    $user->setName('Mr.Smith-' . $i);
    $em->persist($user);
    if (($i % $batchSize) === 0) {
        $em->flush();
        $em->clear(); // Detaches all objects from Doctrine!
    }
}
$em->flush(); //Persist objects that did not make up an entire batch
$em->clear();

谢谢。这与Sogara的答案相同。我会尽快尝试它。 - Cruz
前几天我也在文档页面上遇到了类似的问题,所以想给你提供一个资源,因为我手头正好有。 - OrderAndChaos
太好了!请看我的评论,针对Sogara的回答。 - Cruz

1

对于需要大量内存但不相互依赖的夹具,我通过使用追加标志一次插入一个实体(或较小的实体组)来解决这个问题:

bin/console doctrine:fixtures:load --fixtures="memory_hungry_fixture.file" --append

然后我编写了一个Bash脚本,可以根据需要多次运行该命令。

在您的情况下,您可以扩展Fixtures命令,并设置一个标志来批处理实体 - 首先是前1000行,然后是第二个1000行,以此类推。


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