Symfony/doctrine:优化 n+1 查询

pie*_*ard 3 sql doctrine eager-loading symfony

语境

想象一组提案,其中每个用户都可以对提案投赞成票或反对票。

所以我有2个模型:

  • Proposal
  • A Vote,与 a proposal、 auser和 an opinion(向上或向下)相关。

现在我想显示我的所有提案,包括每个提案的额外信息:

  • 多少赞?
  • 投了多少票?
  • 当前用户是否已投票?
  • 当前用户是否已投反对票?

 问题

这真的很容易实现,但这意味着每个提案显示 4 个请求(称为 n+1 查询问题)。我来自 Rails 世界,在那里可以使用预先加载或一些棘手的查询轻松解决此问题。但是我无法用教义解决它,需要一些帮助!

我试过的

 使用魔法领域

第一种解决方案是进行自定义查询:选择所有提案,包括赞成票、反对票的数量,当前用户是否赞成或反对。

查询现在返回4分列比模型来描述:count_upvotescount_downvoteshas_user_upvotedhas_user_downvoted

为了与学说一起使用,我必须将这些字段添加到我的Proposal实体中。

对我来说,这不是一个好的解决方案,因为它意味着始终使用此查询来避免混蛋模型,其中这些字段可以为空。

使用摘要

第二种解决方案是让视图使用另一个对象。这个对象是用一个参数创建的:当前用户。该对象由优化查询创建,包含以下方法:

  • getAllProposals()
  • getUpvotes($a_proposal)
  • getDownvotes($a_proposal)
  • hasUserUpvoted($a_proposal)
  • hasUserDownvoted($a_proposal)

对我来说,仅仅为了视图优化而被迫创建一个对象真的太过分了。

使用扩展实体

这第三个解决方案使用唯一的查询来获取提案、他们的赞成票、反对票,并检查用户是否赞成或反对。

它创建了一个新的“扩展”实体,

public function __construct(
    Proposal $badgeProposal,
    $upvotesCount,
    $downvotesCount,
    $hasUserUpvoted,
    $hasUserDownvoted
) {
Run Code Online (Sandbox Code Playgroud)

对我来说,这是更好的解决方案,但目前这行不通,因为我不知道如何简单地从 SQL 行中获取建议(但我几乎没有搜索)。

轮到你了

那么,还有什么其他解决方案呢?对于学说开发人员来说,是必须是一个众所周知的问题。

ker*_*ero 5

您是否已经对此进行了实验或只是考虑过?

对我来说,一个简单的方法fetch="EAGER"解决了这个问题,结果正好是一个查询。使用以下设置

class Proposal {
    /**
     * @ORM\OneToMany(targetEntity="Vote", mappedBy="proposals", fetch="EAGER")
     */
    protected $votes;
}

class Vote {
    /**
     * @ORM\ManyToOne(targetEntity="Proposal", inversedBy="votes")
     * @ORM\JoinColumn(..)
     */
    protected $proposal;
    /**
     * @ORM\ManyToOne(targetEntity="Opinion", inversedBy="votes")
     * @ORM\JoinColumn(..)
     */
    protected $opinion;
    /**
     * @ORM\ManyToOne(targetEntity="User", inversedBy="votes")
     * @ORM\JoinColumn(..)
     */
    protected $user;
}

class Opinion/User {
    /**
     * @ORM\OneToMany(targetEntity="Vote", mappedBy="proposals")
     */
    protected $votes;
}
Run Code Online (Sandbox Code Playgroud)

实际上,我对Proposal实体进行了更多编辑,添加了以下方法

class Proposal {
    public function getCountOpinion($id) {
        $count = 0;
        foreach ($this->getVotes() as $vote) {
            if ($vote->getOpinion()->getId() === $id) {
                $count++;
            }
        }
        return $count;
    }

    public function getUserVote($user) {
        foreach ($this->getVotes() as $vote) {
            if ($vote->getUser() == $user) {
                return $vote;
            }
        }
        return null;
    }
}
Run Code Online (Sandbox Code Playgroud)

正如我在开头提到的,这仍然只导致一个查询。然而,你在for这里看到了很多。如果您对每个提案有很多投票,您可能希望缓存结果(例如,只迭代投票一次,获取所有 countOpinion 和 userVote)

PS:不要忘记为具有 ArrayCollections (OneToMany) 的类添加构造函数