【问题标题】:doctrine2 with codeigniter foreign key insert带有codeigniter外键插入的学说2
【发布时间】:2013-12-25 14:50:21
【问题描述】:

我已经关注数据库架构 -

现在部门、年份和部门表已经填满了信息。

我现在需要插入学生数据。学生数据将从 xls 文件中导入(导入和解析部分已完成)。正如您在架构中看到的,student_data 表中的列指的是year_iddepartment_didivision_id。所以在插入时我需要他们的 ID 字段,因为 xls 有各自的名称值。

所以我必须根据每个学生的列值获取相应的 ID。因此,这引入了 3 个查询,以便在学生表中插入一条记录。像这样——

forloop(...):
     $studentData = new Entities\StudentData();

    $year =  $this->em->getRepository("Entities\Year")->findBy(array('year_name' => $this->year[$i]));
    $department =  $this->em->getRepository("Entities\Department")->findBy(array('department_name' => $this->branch[$i]));
    $division =  $this->em->getRepository("Entities\Division")->findBy(array('division_name'=>$this->division[$i]));

    $studentData->setYear($year[0]);
    $studentData->setDepartment($department[0]);
    $studentData->setDivision($division[0]);

    //other data
    .
    .
    .
    .
    .
    $this->em->persist($studentData);

endforloop();   

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

如您所见,我必须为每个部门、年份和部门获取 ID withing 循环。假设我要导入 100 个学生列表,因此它最终会运行 300 个查询以获取这 3 个 ID 字段。

在插入数据时,我可以直接从他们的名字中获取年份、部门和部门的 ID 吗? 我是学说新手,我不知道该怎么做。


更新 如果问题不清楚,请告诉我。我可以用更多细节更新它或重组它。

【问题讨论】:

    标签: php mysql codeigniter doctrine-orm doctrine-query


    【解决方案1】:

    优化

    您可以在不使用 Doctrine 的结果缓存的情况下优化您的流程:

    首先为他们的 id 创建一张年份地图,如下所示:

    $yearsMap = array();
    
    $q = $em->createQuery('SELECT y.id, y.year_name FROM Entities\Year y');
    
    foreach ($q->getScalarResult() as $row) {
        $yearsMap[$row['year_name']] = $row['id'];
    }
    

    还可以根据他们的 ID 创建部门地图,并根据他们的 ID 创建部门地图。 这将产生 3 个(轻量级)查询。 放置此代码的最佳位置是(自定义)存储库。

    接下来你可以运行你的循环,但是像这样“获取”实际的实体:

    $year       = $this->em->getReference('Entities\Year', $yearsMap[$this->year[$i]]);
    $department = $this->em->getReference('Entities\Department', $departmentsMap[$this->branch[$i]]);
    $division   = $this->em->getReference('Entities\Division', $divisionsMap[$this->division[$i]]);
    

    我说“get”,因为getReference() 实际上创建了一个代理(除非它已经被实体管理器加载,但在这种情况下它可能不是)。该代理尚未加载,因此此处不执行任何查询。

    您的其余代码不需要更改。

    现在当flush() 被调用时,Doctrine 将只加载每个不同的年份/部门/部门一次。这仍然可能导致一些查询,具体取决于使用了多少不同年/部门/部门。因此,如果所有 100 名学生都使用不同的年级/部门/部门,您最终将得到 403 个查询(3 个用于地图,300 个用于加载代理,100 个用于插入学生)。但是如果所有 100 名学生都使用相同的年级/部门/部门,那么您最终将只得到 106 个查询(3 个用于地图,3 个用于加载代理,100 个用于插入学生)。

    另一种优化方式

    另一种方法是使用您收集的名称来获取您需要的所有实体:

    $q = $em->createQuery('SELECT y FROM Entities\Year y INDEX BY y.year_name WHERE y.year_name IN(:years)');
    $q->setParameter('years', $yearNames);
    
    $yearsMap = $q->getResult();
    

    您现在只需 1 个查询即可获得所需的所有 Year 实体。您可以对部门和部门执行相同的操作。

    还请注意 DQL 语句中的 INDEX BY:这将确保您将获得一个以 year_name 为键和实体为值的数组。您可以像这样在循环中立即使用它:

    $year       = $yearsMap[$this->year[$i]];
    $department = $departmentsMap[$this->branch[$i]];
    $division   = $divisionsMap[$this->division[$i]];
    

    100 个学生的最终结果将始终是 103 个查询(3 个用于地图,100 个用于插入学生)。

    缓存

    当您需要相对频繁地运行此循环并且它会使数据库紧张时,使用 Doctrine 的 result cache 是明智的。不过有几点需要注意:

    getReference() 不支持结果缓存(目前),结果缓存不会自动使用。所以我建议你把这样的东西放在一个存储库中:

    public function findOneYearByName($name)
    {
        $q = $em->createQuery('SELECT y FROM Entities\Year y WHERE y.year_name = :year');
        $q->setParameter('year', $name);
        $q->useResultCache(true);
    
        return $q->getSingleResult();
    }
    

    您可能想要配置结果缓存,请参阅docs 了解相关内容。

    另一个注意事项是,结果缓存会缓存从数据库中提取的结果,然后再进行水合。因此,即使使用结果缓存,实际实体每次都会被水合。因此我仍然建议使用地图,但实现方式略有不同:

    $yearsMap       = array();
    $departmentsMap = array();
    $divisionsMap   = array();
    
    forloop (...):
        if (!isset($yearsMap[$this->year[$i]])) {
            $yearsMap[$this->year[$i]] = $this->em->getRepository('Entities\Year')->findOneYearByName($this->year[$i]);
        }
    
        if (!isset($departmentsMap[$this->branch[$i]])) {
            $departmentsMap[$this->branch[$i]] = $this->em->getRepository('Entities\Department')->findOneDepartmentByName($this->branch[$i]);
        }
    
        if (!isset($divisionsMap[$this->division[$i]])) {
            $divisionsMap[$this->division[$i]] = $this->em->getRepository('Entities\Division')->findOneDivisionByName($this->division[$i]);
        }
    
        $year       = $yearsMap[$this->year[$i]];
        $department = $departmentsMap[$this->branch[$i]];
        $division   = $divisionsMap[$this->division[$i]];
    

    这将确保每个不同的年份/部门/部门只补水一次。

    PS:将结果缓存用于“以另一种方式优化”的效率不会那么高,因为每次运行循环时,年份/部门/部门的名称都可能不同。每次更改名称时,查询都会更改,并且无法使用缓存的结果。

    DBAL

    在插入数据时,我可以直接从他们的姓名中获取年份、部门和部门的 ID 吗?

    您可以,但您不会使用 ORM,而只会使用 DBAL。你基本上是这样做的:

    $connection = $em->getConnection();
    $statement  = $conn->executeQuery('insert query', array('parameter1', 'etc'));
    $statement->execute();
    

    我怀疑这会更有效,因为 MySQL(或您使用的任何供应商)仍将为每个插入执行这 3 个(子)查询,它们只是不会“越过网络”。而且你没有从 ORM 获得任何帮助,比如管理关联等。

    不过,您仍然可以找到有关 here 主题的所有内容。

    【讨论】:

      【解决方案2】:

      您是否检查过它是否运行了 300 个查询?因为它肯定不应该,除非所有学生有不同年、系部门,这似乎极不可能。如果是这样的话,不管有没有 Doctrine,至少需要 300 个查询,除非有其他优化。

      好消息是,Doctrine 不仅仅是一种访问对象的奇特方式——它是一个完整的数据库抽象层,提供更多的服务,例如a full-blown entity cache。以下行:

      $year =  $this->em->getRepository("Entities\Year")->findBy(array('year_name' => $this->year[$i]));
      

      对于一个给定的年份,这应该最多执行 1 个查询 - 之后,结果将完全存储在 Doctrine 的内部缓存 inside the entity manager 中。这是假设您使用的是股票 MemoryCache,如果您没有指定其他任何内容,则默认启用,它仅在单个请求期间缓存。如果您安装 APC、Memcache、Memcached 甚至 FilesystemCache (pick one!),结果可能会在多个请求中缓存。

      因此,简而言之,您正在想象一个不存在的问题,或者通过几个simple configuration calls 轻松缓解。除非我们讨论的是所有年份、部门和部门都是独一无二的假设情况,否则您确实会触发 300 个查询。然而,这种情况下的问题不是 Doctrine 的问题——它只是按照你的命令去做,分别检索 300 个独特的对象。在这种情况下,没有人会阻止你自己围绕 Doctrine 编写一些智能代码,例如:

      // Build cache before loop
      $years = [];
      foreach($this->em->getRepository("Entities\Year")->findAll() as $year)
        $years[$year->getYearName()] = $year;
      
      // Now loop much faster because everything's already indexed
      forloop(...) :
        $studentData = new Entities\StudentData();
        $studentData->setYear($years[$this->year[$i]]);
      endforloop;
      

      突然之间,您有了 1 个“昂贵”的查询,而不是 100 个稍微便宜的查询。 Doctrine 可以方便地使许多与 DB 相关的编码变得更容易和更有条理,它并不禁止像这样的面向性能的智能编码。最后,您仍然是编码员,而 Doctrine 只是您可以随意使用的一种工具。

      【讨论】:

      • 缓存的东西是一个聪明和最简单的方法。 (把我的头撞在墙上)我傻了才不去想它。即使我访问了那些但留下了关键点,也感谢这些有用的链接。我不擅长整体缓存。我公司的生产服务器没有APC或memcache。我在服务器上安装了 eAccelator,我做了一些研究,发现它不适用于学说。仍然不确定。但我正在我的 Amazon EC2 个人测试服务器上测试 doctine 和 APC,并要求公司也更改他们的服务器。我发现使用 APC 是最快的方法。
      • 感谢链接和有用的点。我仍在寻找可能存在的最佳答案。 :) 所以我会坚持提供赏金。这与赏金或其他东西无关,但如果有的话,我正在寻找可能的最佳答案......即使是未来的访客。
      • 好吧,我不确定您还想要什么更多信息 :) 我很乐意详细说明可能需要澄清的内容。
      • 啊.. 如果这不是问题,我只想了解数据库查询和缓存。我的意思是,学说是否维护每个查询或类似查询的缓存?如果我实例化一个 Entity 的对象,教义会寻找缓存吗?如果有许多表的引用与外键,如果我触发一个查询,教义会得到所有吗?我知道这太多了,但这些只是我怀疑我有(因为它对我来说是新的)但即使这些没有得到回答,随着我更多地使用它,我最终会找到答案。所以如果可能的话,我想知道你对这些的了解。 :)
      猜你喜欢
      • 1970-01-01
      • 2011-09-17
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-04-09
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多