在自引用实体上使用复合键时触发不需要的查询

问题描述 投票:0回答:1

我遇到了一个奇怪的问题,无法理解为什么会发生这种情况。

我有一个实体在

instance
rid
字段上使用组合主键。

我也有

parent
作为与同一实体的自引用关系。

class Subscription
{
    /**
     * @ORM\Column(name="id", type="uuid", unique=true)
     * @JMS\Type("string")
     */
    protected $id;

    /**
     * @ORM\Id
     * @ORM\Column(name="instance_id", type="integer")
     * @JMS\Type("int")
     */
    protected $instance;

    /**
     * @ORM\Id
     * @ORM\Column(name="rid", type="bigint")
     * @ORM\SequenceGenerator(sequenceName="subscriptions_id_seq", initialValue=1, allocationSize=1)
     * @JMS\Type("int")
     */
    protected $rid;

    /**
     * @ORM\ManyToOne(targetEntity="Subscription")
     * @ORM\JoinColumns({
     *     @ORM\JoinColumn(name="parent_int_id", referencedColumnName="rid"),
     *     @ORM\JoinColumn(name="instance_id", referencedColumnName="instance_id")
     * })
     */
    protected $parent;

    ...
}

我面临的问题:

对于以下数据:

id 摆脱 parent_int_id 实例_id
11000000-0000-0000-0000-0000000f37d2 147605 890467 1
11000000-0000-0000-0000-0000000e065f 890467 1
$subscription = $subscriptionService->findOneBy([
    'rid' => 147605
]);

使用

findOneBy()
检索订阅时,我看到 Doctrine 触发了 3 个查询:

这是初始查询,按预期工作:

SELECT t0.id AS id_1, t0.instance_id AS instance_id_2, t0.rid AS rid_3, ...
FROM subscriptions t0 WHERE t0.rid = 147605 LIMIT 1;

这是对父字段的查询,也按预期工作:

SELECT t0.id AS id_1, t0.instance_id AS instance_id_2, t0.rid AS rid_3, ... 
FROM subscriptions t0 WHERE t0.rid = '890467' AND t0.instance_id = 1 LIMIT 1;

此查询是意外的,无法找到禁用它的方法或了解触发它的原因:

SELECT t0.id AS id_1, t0.instance_id AS instance_id_2, t0.rid AS rid_3, ...
FROM subscriptions t0 WHERE t0.instance_id = 1

如您所见,第三个查询尝试选择该

instance_id
的所有行,这是一个问题,因为我们的表非常大(数百万行),这会导致我们的数据库崩溃。检索到的对象已正确构建,并且
parent
属性已按预期填充,但我希望该查询根本不存在。

删除

instance
作为复合键可以停止此行为,但是我们需要它以这种格式工作,所以这不是一个选项。

知道什么可能导致这个额外的查询以及如何摆脱它,以便它按预期工作吗?

symfony doctrine-orm doctrine composite-key self-referencing-table
1个回答
0
投票

好吧,看起来发生的事情是这样的。加载订阅时,在该订阅的水合作用(将数据转换为对象)期间,会遇到多对一关系,除非另有说明,否则将“热切加载”,即立即加载。这会导致第二个查询,这在某种程度上可能非常有用。在这里,同样的事情重复发生。 这与另一个问题相吻合,即关联关系是在

parent_int_id

和始终非空的

instance_id
上定义的。这足以触发对父实体的搜索,并具有简化的条件(仅
instance_id
)(
可能相关的教义来源
- 其中有一个带有 TODO 的部分,所以也许它还没有按预期工作)。这显然不是您想要的。 由于无论如何,所有父实体都是一一获取的,因此可能有充分的理由不急切地获取它,而是“懒惰”地获取它。如果您获取现有实体,这将阻止致命查询的发生。

但是,一旦您访问没有父对象的订阅上的父对象,致命的查询就会再次发生。

可能有一些方法可以防止这种情况发生,但是,我不清楚这是否可以在不深入研究的情况下在理论(或其注释/属性)中实现。唯一的“正确”方法可能是以某种方式教导教条,如果父复合键的一部分为空,则它不应该尝试获取父项(请参阅链接代码,它在某处),这可能涉及覆盖默认实现UnitOfWork 的...这对于 ORM 工作原理非常核心,显然不打算被覆盖。您可能必须编写自己的 UnitOfWork,创建您自己的使用新 UnitOfWork 的 EntityManager。您必须调整您的实现,以在一定程度上与原则保持同步。由于这种定制很少需要,正确的方法可能会很麻烦。 我可以想象一些选项,根据您的软件开发标准和要求,这些选项中的部分或全部可能不合适。不过,这里有一些:

对于

parent_id

,您可以引用 uuid 列而不是复合键(这显然需要 uuid 列上的索引,它应该已经有,因为无论如何它都是唯一的列) - 这可能是最安全的方法,那会起作用的。
  1. 您可以延迟加载父实体,并在父 getter 中确保您永远不会尝试访问非 id 列,否则它将尝试获取对象(这可能会触发致命的查询)。您必须检查父级是否有 id(除了 instance_id 之外),否则返回 null。 - 遗憾的是,我不知道这是否真的有效,延迟加载的代理对象通常为 id 列提供有效的 getter,但我从未尝试过使用复合键。
    添加一个 
  2. parent_instance_id
  3. 列,该列在技术上是多余的,并且会带来自己的问题,但它会起作用,因为当不存在父级时(尚)它可以为空。
  4. 对于这个特定的父关系根本不使用学说关联映射。相反,将其保留为属性,并在请求时从存储库中获取父 ID(从技术上讲,这是选项 2 的降级,但它更容易控制,但关注点分离很脏,因为实体不应该进行查询或获取内容)来自存储库)
    
        
© www.soinside.com 2019 - 2024. All rights reserved.