【问题标题】:symfony2 + doctrine2 - how lazy loading affect our applications?symfony2 + dictionary2 - 延迟加载如何影响我们的应用程序?
【发布时间】:2014-10-03 10:37:35
【问题描述】:

对于所有想了解 symfony2 + 学说 2 和性能的人来说,这将是一种问答。

我写这个 Q/A 是因为我已经开始为一个新项目玩 SF2+doctrine 并且我想做一些性能测试。我在网上冲浪,但没有为我的疑问找到完整的答案,所以我决定自己做一个。希望这对某人有用

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

//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()教义2方法更好还是编写DQL(或使用查询构建器工具)?
为什么?

【问题讨论】:

    标签: symfony doctrine-orm


    【解决方案1】:

    重要

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


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

    1 - findOneById()

    进入控制器,您选择遵循更“常见”和“快速”的方式进行:使用预先存在的findOneById() 方法。太棒了。

    让我们检查一下性能

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

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

    2.1 “基本”DQL 存储库函数

    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;
    }
    

    让我们检查一下性能

    • 已完成的查询数:这对您来说应该不足为奇,但是...已完成的查询数始终为 3!当然,除了 findOneById() 之外,你什么也没做(甚至可能是最糟糕的方式!)。你又在利用延迟加载了。
    • 页面加载时间:大约 500 毫秒。加载时间没有改变
    • 内存使用量:大约 34MB。内存使用量增加了 6.25 %(由于数组?)

    2.2 DQL 与 JOIN

    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;
    }
    

    让我们检查一下性能

    • 查询次数:查询次数不变!但为什么?我们明确告诉教条2 加入currency 实体,但它似乎忽略了该指令。答案是我们也没有选择货币实体,因此教条2 将再次使用延迟加载工具。
    • 页面加载时间:大约 500 毫秒。加载时间没有改变 2.1
    • 内存使用量:大约 34MB。内存使用量与 2.1 相比没有变化

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

    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;
    }
    

    让我们检查一下性能

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

    最终解决方案 (?)

    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;
    }
    

    与其他解决方案相比,$q->setHint(Query::HINT_FORCE_PARTIAL_LOAD, true); 行代码似乎可以节省时间和内存。

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

    请注意

    此解决方案不允许您更改关联的 (Currency) 实体,因为它将与 Query::HINT_FORCE_PARTIAL_LOAD, true 绑定

    评论

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

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


    最后的想法

    最好“浪费”一些时间来编写使用“内置”查询的 DQL 查询(这似乎很愚蠢)。此外,您应该使用数组(而不是对象)进行只读操作(列出无法修改的元素),更改 getResult() 方法调用如下:getResult(Doctrine\ORM\Query::HYDRATE_ARRAY);。这将改变“默认”值(HYDRATE::OBJECT

    【讨论】:

      【解决方案2】:

      在发现问题之前担心它似乎是过早的优化。

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

      查看您编写所有自定义 DQL 所花费的时间,可以通过在应用程序的其他地方修复更大的问题来提高整体性能。

      【讨论】:

      • 不,这不是“过早的”,因为如果您“信任”延迟加载并且您不知道“副作用”,您很容易陷入性能问题(在我的眼中看到一个页面300 查询由于延迟加载!!!)。所以最好知道——至少——一些其他的方法来解决这个问题。顺便说一句,您为什么要谈论“我的代码的整体性能”?它没有什么可做的,因为它只是一个例子,只专注于获取实体,没有其他考虑
      • @DonCallisto 我有一个相当大的应用程序,它到处使用$em->findOneById($id) 样式,它从来都不是性能问题的原因。如果我想寻求最大的性能,我一开始就不会使用 Doctrine(甚至 PHP)。当然,YMMV——如果你的工作是优化 Doctrine 的使用,那么像你在这里给出的建议非常有用,但它应该放在上下文中。
      • Blowski:所以你对我说,当只做 2-3 个查询时,做 40-50 个查询不会导致性能问题?此外,为什么 symfony2 分析器有一个查询分析器?当然 ->findBy 和类似的东西都很好,我并不是说写一个 DQL 来做同样的学说函数。我是说:如果您有一些相关的对象(可能以级联方式),最好编写一个自定义 DQL 来一次获取所有实体和字段......这是我回答的精髓。
      • @DonCallisto 你说得对,40-50 个查询的性能将低于 2-3 个查询。但是如果你只得到 2 或 3 行呢?这个额外的加入可能比几个->findOneById($id) 调用更昂贵。如果您的架构仍然发生很大变化怎么办?现在您需要不断更新您的存储库以及您的控制器和实体。如果初级开发人员偶然发现了您的建议并盲目地实施(他们经常这样做),他们可能会给自己带来更多的问题,而不仅仅是使用 Doctrine 的默认、简单的做事方式。
      • 我同意你评论的结尾部分,但我很确定我的回答提供了很多信息,任何人都可以用自己的方式解释......
      猜你喜欢
      • 2013-12-31
      • 1970-01-01
      • 2010-12-01
      • 1970-01-01
      • 1970-01-01
      • 2018-10-04
      • 1970-01-01
      • 2011-06-28
      • 1970-01-01
      相关资源
      最近更新 更多