sql - Symfony/Doctrine : Optimize n+1 queries

标签 sql symfony doctrine eager-loading

语境
想象一组提案,其中每个用户都可以对提案投赞成票或反对票。
所以我有2个模型:

  • 一个 Proposal ,
  • 一个 Vote , 与 proposal 有关, user , 和 opinion (上或下)。

  • 现在我想显示我的所有提案,包括每个提案的额外信息:
  • 多少赞?
  • 投了多少票?
  • 当前用户是否已投票?
  • 当前用户是否已投反对票?

  • 问题
    这真的很容易实现,但这意味着每个提案显示 4 个请求(称为 n+1 查询问题)。
    我来自 Rails 世界,在那里可以使用预先加载或一些棘手的查询轻松解决这个问题。但我无法用 Doctrine 解决它,需要一些帮助!
    我试过的
    使用魔法领域
    第一种解决方案是进行自定义查询:选择所有提案,包括赞成票、反对票的数量、当前用户是否赞成或反对。
    查询现在返回的列比模型描述的多 4 列:count_upvotes , count_downvotes , has_user_upvotedhas_user_downvoted .
    为了与 Doctrine 一起使用,我必须将这些字段添加到我的 Proposal 中。实体。
    对我来说,这不是一个好的解决方案,因为它意味着始终使用此查询来避免 SCSS 模型,其中这些字段可以为空。
    使用摘要
    第二种解决方案是让 View 使用另一个对象。这个对象是用一个参数创建的:当前用户。该对象由优化查询创建,包含以下方法:
  • getAllProposals()
  • getUpvotes($a_proposal)
  • getDownvotes($a_proposal)
  • hasUserUpvoted($a_proposal)
  • hasUserDownvoted($a_proposal)

  • 对我来说,仅仅为了 View 优化而被迫创建一个对象真的太过分了。
    使用扩展实体
    这第三个解决方案使用唯一的查询来获取提案、他们的赞成票、反对票,并检查用户是否赞成或反对。
    它创建了一个新的“扩展”实体,
    public function __construct(
        Proposal $badgeProposal,
        $upvotesCount,
        $downvotesCount,
        $hasUserUpvoted,
        $hasUserDownvoted
    ) {
    
    对我来说,这是更好的解决方案,但目前这行不通,因为我不知道如何简单地从 SQL 行中获取建议(但我几乎没有搜索)。
    轮到你了
    那么,还有什么其他解决方案呢?对于 Doctrine 开发人员来说,是必须是一个众所周知的问题。

    最佳答案

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

    对我来说,一个简单的 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;
    }
    

    实际上,我编辑了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;
        }
    }
    

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

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

    关于sql - Symfony/Doctrine : Optimize n+1 queries,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/37497843/

    相关文章:

    php - Doctrine ZFA\排除字段显示在 IIS 上的表单上

    doctrine - Symfony 4 如何实现 Doctrine XML ORM 映射

    mysql - SQL语句从多个表中提取记录

    mysql - 在同一列上使用多个WHERE条件进行选择

    php - 奏鸣曲日期范围过滤器

    php - 使用 Doctrine Symfony2 中的查询生成器实现 SQL 联合查询

    mysql - 查询时间长

    java - 替换过大的复合键

    sql - Access SQL 中的合并替代方案

    Symfony2,DoctrineFixturesBundle,由于外键约束无法加载夹具