symfony2 + doctrine2 - 延迟加载如何影响我们的应用程序?

时间:2014-10-03 10:37:36

标签: symfony doctrine-orm

对于每个想知道symfony2 + doctrine2和性能的人来说,这都是一种Q / A.

我正在编写此Q / A,因为我已经开始围绕SF2 +学说参与一个新项目,我想做一些性能测试。我已经上网但没有找到完整的答案,因此我决定自己制作一个。希望这对某人有用

我们假设我们有两个实体,如下所示

//all ORM declaration here
class Lodging
{
  //some declaration here

  /**
   * @ORM\ManyToOne(targetEntity="Currency")
   * @ORM\JoinColumn(name="currency_iso_code", referencedColumnName="iso_code")
   */
  protected $currency;

  //some methods here
}

//all ORM declaration here 
class Currency
{
  /**
   * @ORM\Id
   * @ORM\Column(type="string", length=3)
   */
  protected $iso_code;

  /**
   * @ORM\Column(type="string")
   */
  protected $description;

  //some methods here
}

现在,您将为Lodging创建一个实体类型,您可以在其中创建或 - 如果传递给该类型的实体具有一些预取值 - 来编辑Lodging对象(让我们专注于那个案子)。在该表单中,您还需要Currency的说明,而不仅仅是$iso_code

问题:最好使用->findOneById() doctrine2方法或编写DQL(或使用查询构建器工具)?
为什么呢?

2 个答案:

答案 0 :(得分:3)

PREMABLE

只有当您遇到使用延迟加载的页面时,此答案才有意义。对于更常见的操作(只加载一个实体,加载“静态页面”或缓存的操作等),您可以继续使用doctrine提供的内置函数


好吧,让我们分析一下您可以遵循的一些不同的方法

1 - findOneById()

在控制器中,您选择遵循更多“常用”和“快速”方式进行处理:使用预先存在的findOneById()方法。精彩。

让我们检查一下性能

  • 完成的查询次数 :完成了三个查询,因为我们需要一个查询Lodging对象,第二个查询当前关联{{1} (延迟加载)和一个获取所有Currency实体(因为您可以从一种货币更改为其他货币)
  • 页面加载时间 :约500毫秒
  • 内存使用情况 :约32MB

2 - 编写自定义存储库方法

2.1“基本”DQL存储库功能

Currency

让我们检查一下性能

  • 完成的查询次数 :这对您来说不应该是一个惊喜,但......完成的查询次数总是三次!当然你除了findOneById()之外什么都不做(也许,甚至以最坏的方式!)。你再次利用延迟加载。
  • 页面加载时间 :约500毫秒。加载时间没有改变
  • 内存使用情况 :约34MB。内存使用量增加了6,25%(由于阵列?)

2.2 DQL with JOIN

public function findLodgingById($id)
{
  $lodging = null;

  $q = $this->createQueryBuilder('lodging')
         ->select('lodging')
         ->where('lodging.id = :id')
         ->setParameter('id', $id)
         ->getQuery();

  $lodging_array = $q->getResult(); //will not fetch a single object but array
  if ($lodging_array) {
    $lodging = reset($lodging_array);
  }

  return $lodging;
}

让我们检查一下性能

  • 完成的查询次数 :查询次数不会改变!但为什么?我们明确告诉doctrine2加入public function findLodgingById($id) { $lodging = null; $q = $this->createQueryBuilder('lodging') ->select('lodging') ->leftJoin('lodging.currency', 'currency') ->where('lodging.id = :id') ->setParameter('id', $id) ->getQuery(); $lodging_array = $q->getResult(); //will not fetch a single object but array if ($lodging_array) { $lodging = reset($lodging_array); } return $lodging; } 实体,但似乎忽略了该指令。答案是我们不是 选择 货币实体,因此doctrine2将再次使用延迟加载工具。
  • 页面加载时间 :约500毫秒。加载时间没有变化2.1
  • 内存使用情况 :约34MB。内存使用率没有从2.1
  • 更改

2.3让我们尝试更好的方法:加入货币选择

currency

让我们检查一下性能

  • 完成的查询次数 :最后查询次数减少了!我们达到两个查询而不是三个。什么查询消失了?延迟关联(当前)货币的加载已经消失,但当然,您必须获取所有可能的货币。
  • 页面加载时间 :约350毫秒。
  • 内存使用情况 :约34MB。内存使用情况没有改变

最终解决方案(?)

public function findLodgingById($id)
{
  $lodging = null;

  $q = $this->createQueryBuilder('lodging')
         ->select('lodging', 'currency')
         ->leftJoin('lodging.currency', 'currency')
         ->where('lodging.id = :id')
         ->setParameter('id', $id)
         ->getQuery();

  $lodging_array = $q->getResult(); //will not fetch a single object but array
  if ($lodging_array) {
    $lodging = reset($lodging_array);
  }

  return $lodging;
}

与其他解决方案相比,public function findLodgingById($id) { $lodging = null; $q = $this->createQueryBuilder('lodging') ->select('lodging') ->where('lodging.id = :id') ->setParameter('id', $id) ->getQuery(); $q->setHint(Query::HINT_FORCE_PARTIAL_LOAD, true); $lodging_array = $q->getResult(); //will not fetch a single object but array if ($lodging_array) { $lodging = reset($lodging_array); } return $lodging; } 代码行似乎可以节省时间和内存。

  • 完成的查询次数 :当然有两个
  • 页面加载时间 :约340毫秒。
  • 内存使用情况 :约32MB。

请注意

此解决方案不允许您更改关联的($q->setHint(Query::HINT_FORCE_PARTIAL_LOAD, true);)实体,因为它将与Currency

相关联

评论

结果似乎对页面加载时间有好处(内存使用当然不会改变)虽然性能似乎只是“稍微好一点”,但你不应该只关注结果:这里我们采取的一个示例只是一个简单的代码片段,只有一个延迟加载操作:想一个实体(或者,最糟糕的是,很多实体,比如带有相关注释的博客文章)会对每个获取和管理的实体进行错误的(*)延迟加载:对于单个页面,您甚至可以达到50-70查询(当然,在这种情况下,由于“单个”查询,您可以轻松地注意到性能优势)

(*)为什么我说错了?因为如果您可以将对象的提取逻辑迁移到其他地方,或者您已经知道所需的实体/属性,那么您的内容不是动态,您可以在使用之前了解它们,延迟加载不仅无用而且有害。
相反,如果你不能在“编写代码时”知道你需要什么属性或实体,当然延迟加载可以节省你浪费在无用的对象/关联上的记忆(和时间)。


最后的想法

最好“丢失”使用“内置”查询的写DQL查询(似乎甚至是愚蠢的)几分钟。此外,您应该使用数组(而不是对象)进行只读操作(无法修改的列表元素)更改Query::HINT_FORCE_PARTIAL_LOAD, true方法调用,如下所示:getResult()。这将更改“默认”值(getResult(Doctrine\ORM\Query::HYDRATE_ARRAY);

答案 1 :(得分:0)

在看到问题之前,过早的优化似乎要担心它。

写出最快的东西,在这种情况下,通常会是$em->findOneById($id)样式。然后使用valgrind寻找瓶颈。

考虑编写所有自定义DQL所花费的时间,可以通过在应用程序中的其他位置修复更大的问题来提高整体性能。