我跟随数据库架构 -
现在部门,年份和部门表已经填满了信息。
我现在需要插入学生数据。学生数据将从xls文件导入(导入和解析部分已完成)。正如您在架构中看到的那样,student_data
表中的列引用了year_id
,department_di
和division_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循环。假设我导入了100个学生列表,因此它最终会运行300个查询来获取这3个ID字段。
我可以在插入数据时直接从姓名中获取年份,部门和部门的ID吗? 我是学说新手,我不知道怎么做。
更新 如果问题不清楚,请告诉我。我可以用更多细节更新它或重组它。
答案 0 :(得分:4)
<强>优化强>
您可以在不使用Doctrine的结果缓存的情况下优化您的流程:
首先像这样创建一个年份的地图:
$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上的所有内容。
答案 1 :(得分:3)
您是否检查过它是否运行了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是一种方便的工具,可以使很多与数据库相关的编码变得更容易和更有条理,它不会禁止像这样的智能性能编码。最后你仍然是编码员,而且Doctrine只是你腰带中的一个工具,你可以按照自己的意愿运用。