用单表继承连接两个模型,第二次连接会减慢速度


Doctrine Joining two Models with single-table inheritance, second join slows down

我有3个模型使用单表继承。它们适用于三种不同类型的物品,可以在我们的网站上购买。项目被放入类别中,因此Category模型具有映射三种类型中的每一种的属性。

当使用一个简单的选择来获取所有类别,然后显示它们的名称和类别中每种类型的项目的数量时,Doctrine总共在549毫秒内执行361次查询。(一个用于类别列表,另一个用于类别内的每种类型)

所以我开始在查询中添加连接,以消除所有额外的查询。对于第一个项目类型,它工作得很好,主查询运行101.80毫秒(根据Symfony Profiler工具栏)

$this->_em->createQueryBuilder()
        ->select([$alias, 'courses'])
        ->from($this->_entityName, $alias)
        ->leftJoin("{$alias}.courses", 'courses');
当我添加第二个连接时,查询速度减慢到24050.14 ms
$qb = $this->_em->createQueryBuilder()
        ->select([$alias, 'courses', 'bundles'])
        ->from($this->_entityName, $alias)
        ->leftJoin("{$alias}.courses", 'courses')
        ->leftJoin("{$alias}.bundles", 'bundles');

我甚至还没有尝试过第三次连接,担心它会使服务器崩溃。

真正奇怪的是,如果我使用学说查询日志并获得确切的查询,并手动运行它对我的数据库,它运行在只有0.2秒。

表在所有FK列和discriminator列上都有索引。

如有任何建议,我将不胜感激。谢谢!

编辑:4/3

我必须让分支回到其他问题的工作点上来处理这个问题。

SQL Fiddle with Schema(减去在这种情况下未加载的其他表的fk): http://sqlfiddle.com/#!2/85051

所以,我有一个没有连接的页面,它在820.38毫秒内进行了382次查询,用于延迟加载。当我手动连接而不是依赖于延迟加载时,它从130到21159(这只连接了2个模型,所以它仍然延迟加载第三个)

$qb = $this->_em->createQueryBuilder()
        ->select([$alias, 'courses', 'bundles'])
        ->from($this->_entityName, $alias)
        ->leftJoin("{$alias}.courses", 'courses')
        ->leftJoin("{$alias}.bundles", 'bundles');

这是来自Symfony工具栏的查询(20241.26 ms)

    SELECT 
  i0_.description AS description0, 
  i0_.id AS id1, 
  i0_.name AS name2, 
  i0_.created_at AS created_at3, 
  i0_.updated_at AS updated_at4, 
  i0_.display_order AS display_order5, 
  i1_.atccode AS atccode6, 
  i1_.version AS version7, 
  i1_.description AS description8, 
  i1_.online_price AS online_price9, 
  i1_.mail_price AS mail_price10, 
  i1_.is_featured AS is_featured11, 
  i1_.code AS code12, 
  i1_.hours AS hours13, 
  i1_.summary AS summary14, 
  i1_.seo_keywords AS seo_keywords15, 
  i1_.seo_description AS seo_description16, 
  i1_.asha_code AS asha_code17, 
  i1_.preview_name AS preview_name18, 
  i1_.preview_link AS preview_link19, 
  i1_.preview_type AS preview_type20, 
  i1_.is_active AS is_active21, 
  i1_.id AS id22, 
  i1_.name AS name23, 
  i1_.created_at AS created_at24, 
  i1_.updated_at AS updated_at25, 
  i1_.deleted_at AS deleted_at26, 
  i1_.goals AS goals27, 
  i1_.disclosure_statement AS disclosure_statement28, 
  i1_.embedded_video AS embedded_video29, 
  i1_.broadcast_chat_link AS broadcast_chat_link30, 
  i2_.atccode AS atccode31, 
  i2_.version AS version32, 
  i2_.description AS description33, 
  i2_.online_price AS online_price34, 
  i2_.mail_price AS mail_price35, 
  i2_.is_featured AS is_featured36, 
  i2_.code AS code37, 
  i2_.hours AS hours38, 
  i2_.summary AS summary39, 
  i2_.seo_keywords AS seo_keywords40, 
  i2_.seo_description AS seo_description41, 
  i2_.asha_code AS asha_code42, 
  i2_.preview_name AS preview_name43, 
  i2_.preview_link AS preview_link44, 
  i2_.preview_type AS preview_type45, 
  i2_.is_active AS is_active46, 
  i2_.id AS id47, 
  i2_.name AS name48, 
  i2_.created_at AS created_at49, 
  i2_.updated_at AS updated_at50, 
  i2_.deleted_at AS deleted_at51, 
  i0_.created_by AS created_by52, 
  i0_.updated_by AS updated_by53, 
  i1_.type AS type54, 
  i1_.item_format_id AS item_format_id55, 
  i1_.company_id AS company_id56, 
  i1_.created_by AS created_by57, 
  i1_.updated_by AS updated_by58, 
  i1_.format_video_id AS format_video_id59, 
  i1_.exam_id AS exam_id60, 
  i1_.survey_id AS survey_id61, 
  i1_.royalty_owner_id AS royalty_owner_id62, 
  i2_.type AS type63, 
  i2_.item_format_id AS item_format_id64, 
  i2_.company_id AS company_id65, 
  i2_.created_by AS created_by66, 
  i2_.updated_by AS updated_by67 
FROM 
  item_category i0_ 
  LEFT JOIN item_category_assignment i3_ ON i0_.id = i3_.item_category_id 
  LEFT JOIN item i1_ ON i1_.id = i3_.item_id 
  AND i1_.type IN ('Product') 
  AND (
    (
      i1_.deleted_at IS NULL 
      OR i1_.deleted_at > '2014-04-03 13:50:45'
    )
  ) 
  LEFT JOIN item_category_assignment i4_ ON i0_.id = i4_.item_category_id 
  LEFT JOIN item i2_ ON i2_.id = i4_.item_id 
  AND i2_.type IN ('Bundle') 
  AND (
    (
      i2_.deleted_at IS NULL 
      OR i2_.deleted_at > '2014-04-03 13:50:45'
    )
  ) 
WHERE 
  i0_.id IN (
    '108', 
    '175', 
    '100', 
    '202', 
    '198', 
    '203', 
    '199', 
    '200', 
    '201', 
    '197', 
    '101', 
    '98', 
    '102', 
    '131', 
    '105', 
    '41', 
    '72', 
    '64', 
    '73', 
    '194', 
    '195', 
    '29', 
    '189', 
    '139', 
    '103', 
    '37', 
    '99', 
    '14', 
    '110', 
    '193', 
    '80', 
    '111', 
    '68', 
    '183', 
    '39', 
    '71', 
    '53', 
    '66', 
    '178', 
    '179', 
    '180', 
    '176', 
    '174', 
    '75', 
    '17', 
    '32', 
    '81', 
    '181', 
    '182', 
    '74', 
    '104', 
    '184', 
    '26', 
    '49', 
    '190', 
    '191', 
    '36', 
    '24', 
    '85', 
    '30', 
    '107', 
    '91', 
    '90', 
    '185', 
    '23', 
    '196', 
    '60', 
    '89', 
    '21', 
    '95', 
    '65', 
    '28', 
    '33', 
    '58', 
    '187', 
    '9', 
    '132', 
    '12', 
    '43', 
    '192', 
    '5', 
    '62', 
    '40', 
    '87', 
    '7', 
    '83', 
    '27', 
    '6', 
    '86', 
    '10', 
    '13', 
    '15', 
    '70', 
    '69', 
    '121', 
    '67', 
    '93', 
    '97', 
    '92', 
    '94', 
    '188', 
    '177', 
    '82', 
    '96', 
    '42', 
    '137', 
    '19', 
    '11', 
    '63', 
    '20', 
    '51', 
    '57', 
    '8', 
    '22', 
    '48', 
    '35', 
    '4', 
    '135', 
    '61', 
    '186', 
    '106', 
    '109', 
    '88', 
    '16', 
    '31', 
    '34'
  ) 
ORDER BY 
  i0_.name ASC

AND the explain for it:

1   SIMPLE       i0_    ALL     PRIMARY                                                                       126   Using where; Using filesort  ,
1   SIMPLE       i3_    ref     item_category_id         item_category_id  4        cems-staging.i0_.id       6                                  ,
1   SIMPLE       i1_    eq_ref  PRIMARY                    PRIMARY           4        cems-staging.i3_.item_id  1     Using where                  ,
1   SIMPLE       i4_    ref     item_category_id         item_category_id  4        cems-staging.i0_.id       6                                  ,
1   SIMPLE       i2_    eq_ref  PRIMARY                    PRIMARY           4        cems-staging.i4_.item_id  1     Using where   

* PHP

这是Items模型的映射:

 /**
 * @var ArrayCollection
 * @ORM'ManyToMany(targetEntity="models'Category")
 * @ORM'JoinTable(name="item_category_assignment",
 *    joinColumns={@ORM'JoinColumn(name="item_id", referencedColumnName="id")},
 *    inverseJoinColumns={@ORM'JoinColumn(name="item_category_id", referencedColumnName="id")}
 * )
 */
protected $categories;

和类别模型

上的映射
    /**
 * @var ArrayCollection
 *
 * @ORM'ManyToMany(targetEntity="models'Course")
 * @ORM'JoinTable(name="item_category_assignment",
 *    inverseJoinColumns={@ORM'JoinColumn(name="item_id", referencedColumnName="id")},
 *    joinColumns={@ORM'JoinColumn(name="item_category_id", referencedColumnName="id")}
 * )
 */
protected $courses;
/**
 * @var ArrayCollection
 *
 * @ORM'ManyToMany(targetEntity="models'Bundle", mappedBy="categories", orphanRemoval=true)
 * @ORM'JoinTable(name="item_category_assignment",
 *    inverseJoinColumns={@ORM'JoinColumn(name="item_id", referencedColumnName="id")},
 *    joinColumns={@ORM'JoinColumn(name="item_category_id", referencedColumnName="id")}
 * )
 */
protected $bundles;
/**
 * @var ArrayCollection
 *
 * @ORM'ManyToMany(targetEntity="models'Package", mappedBy="categories", orphanRemoval=true)
 * @ORM'JoinTable(name="item_category_assignment",
 *    inverseJoinColumns={@ORM'JoinColumn(name="item_id", referencedColumnName="id")},
 *    joinColumns={@ORM'JoinColumn(name="item_category_id", referencedColumnName="id")}
 * )
 */
protected $packages;

对象和属性与行和列之间的区别在这里并不重要。

你正在尝试做一系列的LEFT JOIN s,你可能应该使用UNION s。

请参阅微软对CROSS JOIN和笛卡尔产品的看法。他们总结得很好。

编辑:我认为这回答了"为什么我的第二次连接慢?"的问题。

你的意思是问:"请帮我改进我的3模型Doctrine查询,使其运行时间少于550ms。"div ?

我将用简单的术语来解释它,我认为您正在尝试将3个名为item的表连接到项目类别项目类别分配所以可能想要这样:

    SELECT item.id, item.name, item.value FROM item
       LEFT JOIN item category
         INNER JOIN item category assignment
       ON item category.id = item category assignment.id
       ON item category.id = item.id

通过中间表项类别连接到项类别赋值上。因为项目和项目类别之间的连接是LEFT join,所以您将获得所有项目记录。

可选:

         SELECT item.id, item.name, item.value FROM item
         LEFT JOIN item category ON item category.id = item.id
         LEFT JOIN item category assignment ON item category.id = item category assignment.id