使用相关的长集合保存一个实体表单的性能

时间:2014-09-11 23:18:56

标签: forms performance symfony doctrine-orm

我有两个实体

EntityA:
  id: ~
  name: ~

EntityB:
  id: ~
  name: ~
  entityA: ~
  unique: [entityA, name]

当我提交新的EntityB时,我有大约10000个实体EntityA

curl -X POST http://localhost/api/entityb.json -d {name: "Hello World", entityA: "017cbc11-95be-4280-8093-9e5b641d73a5"}

我的商务逻辑非常简单

protected function process(array $parameters, EntityB $object, EntityBType $type, $method = 'PUT')
{
    $form = $this->getFormfactory()->create($type, $object);
    $form->submit($parameters, 'PATCH' !== $method);

    if ($form->isValid()) {
        $this->getEm()->persist($object);
        $this->getEm()->flush();

        //... returns 201
    }

    // return 400
}

EntityB FormType

class EntityBType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder
            ->add('id')
            ->add('name')
            ->add('entityA', null, array(
                'property' => 'id'
            ))
        ;
    }

    public function setDefaultOptions(OptionsResolverInterface $resolver)
    {
        $resolver->setDefaults(array(
            'data_class' => 'ApplicationBundle\Entity\EntityB'
        ));
    }

    /**
     * {@inheriDoc}
     */
    public function getName()
    {
        return '';
    }
}

EntityB

use Doctrine\ORM\Mapping\Column;
use Doctrine\ORM\Mapping\Entity;
use Doctrine\ORM\Mapping\GeneratedValue;
use Doctrine\ORM\Mapping\Id;
use Doctrine\ORM\Mapping\ManyToOne;
use Doctrine\ORM\Mapping\Table;
use Doctrine\ORM\Mapping\UniqueConstraint;
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
use Symfony\Component\Validator\Constraints\NotNull;

/**
 * Class EntityB
 * @package ApplicationBundle\Entity\EntityB
 * @Entity()
 * @Table(
 *      name="entity_b",
 *      uniqueConstraints={
 *          @UniqueConstraint(name="entity_b_uniq", columns={"entity_a_id", "name"})
 *      }
 * )
 * @UniqueEntity(fields={"entityA", "name"})
 */
class EntityB {
    /**
     * @var string
     * @Id()
     * @GeneratedValue(strategy="UUID")
     * @Column(name="id", length=36)
     */
    protected $id;

    /**
     * @var string
     * @Column(name="name", length=250)
     * @NotNull()
     */
    protected $name;

    /**
     * @var EntityA
     * @ManyToOne(targetEntity="ApplicationBundle\Entity\EntityA")
     */
    protected $entityA;

    // setter and getters ...
}

采取aprox。 2300ms这太多了!在我的开发环境中,保存aprox是300ms。我认为这里的问题是当我使用从数据库中获取所有EntityA的形式验证eht entityB时,我怎么能解决这个问题?我正在开发的系统需要50000到100000个EntityBs

执行查询

  1. 时间:14.40 ms

    SELECT 
      t0.id AS id1,  
      t0.name AS name4
    FROM 
      entityA t0
    
  2. 时间:0.64毫秒

    SELECT 
      t0.id AS id1, 
      t0.name AS name2, 
      t0.entity_a_id AS entity_a_id3 
    FROM 
      entity_b t0 
    WHERE 
      t0.entity_a_id = ? 
      AND t0.name = ?
    
    Parameters: [017cbc11-95be-4280-8093-9e5b641d73a5, 'Hello World'] 
    
  3. 时间0.28毫秒

    SELECT UUID()
    
  4. 时间0.23毫秒

    START TRANSACTION
    
  5. 时间:0.49毫秒

    INSERT INTO entity_b (id, name, entity_a_id) 
    VALUES 
      (?, ?, ?)
    
    Parameters: { 1: fb2dd6c9-3989-11e4-a5d6-1867b083cd22, 2: 'Hello World!', 3: 017cbc11-95be-4280-8093-9e5b641d73a5 } 
    
  6. 时间:240.54毫秒

    COMMIT
    
  7. 这让我觉得问题是因为当你得到EntityA的列表时,因为控制器中的时间线显示我2318ms(doctrine使用了289ms),这里的问题可能是表单验证器组件

2 个答案:

答案 0 :(得分:1)

所以我要在这里进行一次飞跃并猜测:

class EntityBType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder
            ->add('id')
            ->add('name')
            ->add('entityB', null, array(
            'property' => 'id'
        ))
        ;
    }

实际上是这样的:

class EntityBType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder
            ->add('name')
            ->add('entityA', 'entity', array(
            'class' => 'EntityA',
            'property' => 'id'
        ))
        ;
    }

设置type = entity实际上会默认加载整个EntityA表,这解释了性能问题。当人们发布他们认为代码应该是什么样的内容而不是实际内容时,我总是笑一笑。

要取消默认查询,请按以下步骤操作:http://symfony.com/doc/current/reference/forms/types/entity.html#using-choices

由于您已经拥有EntityA id,只需查询它,然后将其作为choices参数传递。我不会尝试为您提供准确的代码,因为您的问题中的内容与您的实际代码不符,因此浪费时间。

但这应该让你去。

答案 1 :(得分:0)

doctrine manager实际上并不是设计用于处理超过1000个实体同时保存...

优化它的好处是刷新每500或1000个实体,然后清除学说管理器或最终重新创建你的学说管理器对象。

  // your loop through object to save
  $a = 0;
  foreach ( $dataCollection as $data )
  {
     $newB = new entityB();
      ///// settings for entityB
     $entityManager->persist( $newB );
     $a++;

     // we reached 1000 entitiesB so we flush and clear
     if ( $a%1000 == 0 )
      {

         $entityManager->flush();
         $entityManager->clear(); // we clean our entity manager queue
     }
  }