【问题标题】:Import massive csv data with symfony command too slow with doctrine使用 symfony 命令导入大量 csv 数据太慢了
【发布时间】:2019-09-25 07:38:50
【问题描述】:

我需要使用 Symfony 从 myqsl 数据库中的 csv 文件 (45 Mo) 导入大量数据。我导入了 League\Csv\Reader 库 我用教义发出命令。 它有效,但我很慢。 我怎样才能加快这个速度?

我试过了:

  1. 在 $this->em->flush() 之后添加:$this->em->clear();

  2. adding : //禁用 SQL 日志记录:以避免大量内存丢失。 $this->em->getConnection()->getConfiguration()->setSQLLogger(null);

namespace App\Command;

use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
use App\Entity\Developer;
use App\Entity\BadgeLabel;
use Doctrine\ORM\EntityManagerInterface;
use League\Csv\Reader;

class CsvImportCommand extends Command
{
    public function __construct(EntityManagerInterface $em){

        parent::__construct();

        $this->em = $em;
    }

    // the name of the command (the part after "bin/console")
    protected static $defaultName = 'app:import-developpers';

    protected function configure()
    {
        $this
        // the short description shown while running "php bin/console list"
        ->setDescription('Import a new developper.')

        // the full command description shown when running the command with
        // the "--help" option
        ->setHelp('This command allows you to import a develpper...')
    ;

    }

    protected function execute(InputInterface $input, OutputInterface $output)
    {
        $io = new SymfonyStyle($input, $output);
        $io->title('Importation en cours');


        $reader = Reader::createFromPath('%kernel.root_dir%/../src/Data/developers_big.csv')
            ->setHeaderOffset(0)
        ;
        $results = $reader->getrecords();
        $io->progressStart(iterator_count($results));

        //Disable SQL Logging: to avoid huge memory loss.
        $this->em->getConnection()->getConfiguration()->setSQLLogger(null);


        foreach ($results as $row) {
            $developer = $this->em->getRepository(Developer::class)
            ->findOneBy([
                'firstName' => ($row['FIRSTNAME']),
                'lastName'=> ($row['LASTNAME'])
            ])
            ;

            if (null === $developer) {
                $developer = new developer;
                $developer
                    ->setFirstName($row['FIRSTNAME'])
                    ->setLastName($row['LASTNAME']);
                $this->em->persist($developer);
                $this->em->flush();
                $this->em->clear();             
            }

            $badgeLabel = $this->em->getRepository(BadgeLabel::class)
                ->findOneBy([
                    'name' => ($row['BADGE LABEL']),
                    'level'=> ($row['BADGE LEVEL'])
                ])
            ;

            if (null === $badgeLabel) {
                $badgeLabel = new BadgeLabel;
                $badgeLabel
                    ->setName($row['BADGE LABEL'])
                    ->setLevel($row['BADGE LEVEL']);
                $this->em->persist($badgeLabel);
                $this->em->flush();
                $this->em->clear();

            }
            $developer
                ->addBadgeLabel($badgeLabel);

            $io->progressAdvance();
        }

        $this->em->flush();
        $this->em->clear();

        $io->progressFinish();
        $io->success('Importation terminée avec succès');
    }
}

该命令运行缓慢。 15 分钟后,只有 32% 的数据上传到我的 Mysql 数据库中。我预计最多 2 分钟

【问题讨论】:

  • 您应该避免循环中的两个“findOneBy”。尝试在 foreach 之外获取所有开发人员和徽章标签。也看看学说批处理doctrine-project.org/projects/doctrine-orm/en/2.6/reference/…
  • 您应该使用像 XHProf 或 blackfire.io 这样的分析器。这将告诉您代码的哪些部分正在减慢速度,您可以更直接地解决这些问题。作为一般建议,您可能希望避免 Doctrine ORM 并使用 DBAL 来避免不必要的对象水合,例如fpr $badgeLabel$developer 你不需要实际的对象。如果可能,您可能还希望一次对多个开发人员/徽章执行批处理操作,以减少查询量。这是否有必要在很大程度上取决于您的分析器所说的问题。

标签: php symfony doctrine thephpleague


【解决方案1】:

方法1:(不是最好的)

flush 方法被调用时,Symfony 会遍历所有的监听器。因此,您可以避免在每个循环上刷新。您可以用以下代码替换每个刷新:

if (0 === ($batchSize++ % $input->getOption('fetch'))) {
    $this->entityManager->flush();
    $this->entityManager->clear();
}

fetch选项可以在configure方法中声明:

    const BATCH_SIZE = 1000; // As example

    /**
     * Configure the command.
     */
    protected function configure()
    {
        $this
        // the short description shown while running "php bin/console list"
        ->setDescription('Import a new developper.')

        //This option helps you to find a good value and use BATCH_SIZE constant as default
        ->addOption('fetch', 'f', InputArgument::OPTIONAL, 'Number of loop between each flush', self::BATCH_SIZE)

        // the full command description shown when running the command with
        // the "--help" option
        ->setHelp('This command allows you to import a develpper...')
    ;

方法2:更高效

您可以创建一个命令,该命令通过更新或插入将所有 SQL 查询写入 sql 文件。然后,您启动一​​个读取文件并执行查询的本机命令。

方法3:使用DBAL 正如 cmets 中所建议的,您可以使用 DBAL 来避免 Doctrine 不必要的对象水合。

【讨论】:

  • 好的,谢谢您的回复。我将尝试使用 rawSQL 的方法 2,我会告诉你
  • 我添加了第三种方法,这可能是一个很好的折衷方案。不要犹豫,将您的结果转发给我们。
  • @AlexandreTranchant 对于method2,如何访问查询?我得到了所有计划的实体 (em->getUOW->getScheduledEntityInsertions()) 但如何获取查询?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2017-06-07
  • 2016-08-05
  • 1970-01-01
  • 1970-01-01
  • 2022-01-14
  • 2020-01-31
  • 1970-01-01
相关资源
最近更新 更多