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

lnvxswe2  于 2023-11-22  发布在  其他
关注(0)|答案(1)|浏览(145)

我遇到了一个奇怪的问题,不明白为什么会发生这种情况。
我有一个实体在instancerid字段上使用了一个组合主键。
我也有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 - 0000000f37d2| 147605 | 890467 | 1 |
| 11000000 - 0000 - 0000 - 0000000e065f| 890467 |null| 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作为复合键可以停止这种行为,但是我们需要它以这种格式工作,所以它不是一个选项。
有什么想法可能会导致这个额外的查询,以及如何摆脱它,所以它将按预期工作?

dxxyhpgq

dxxyhpgq1#

好的,看起来发生的是这样的。当订阅被加载时,在订阅的水合过程中(将数据转换为对象),遇到了多对一关系,除非另有说明,否则它将被 * 急切地加载 *,即立即加载。这导致了第二个查询,这在某种程度上可能非常有用。在这里,同样的事情重复。
这与另一个问题相吻合,即关联关系定义在parent_int_id和总是非空的instance_id上。(仅instance_id)(可能相关的教义来源-其中有一部分是TODO,所以也许它还没有像预期的那样工作)。这显然不是你想要的。
由于所有父实体都是一个接一个地获取的,所以可能有很好的理由不急于获取它,但是lazily。如果您获取现有实体,这将阻止致命查询的发生。但是,一旦您访问没有父实体的订阅上的父对象,致命查询就会再次发生。
一般来说,可能有办法防止这种情况发生,但是,我不清楚这是否可以在理论上实现唯一“正确”的方法可能是以某种方式教导这样的原则:如果父组合键的一部分为空,则不应尝试获取父组合键(见链接代码,它在那里的某个地方),这可能涉及到修改UnitOfWork的默认实现.创建您自己的使用新UnitOfWork的AlternityManager。您必须调整您的实现以保持与教义的同步。由于这种定制很少需要,正确的方式可能会太麻烦。
根据您的软件开发标准和要求,我可以想象一些选项,其中一些或所有选项可能不适合。然而,这里有一些:
1.对于parent_id,你可以引用uuid列而不是复合键(这显然需要uuid列的索引,它应该已经有了,因为它是一个唯一的列)-这可能是最安全的方法,它会工作。
1.你可以延迟加载父实体,并在你的父getter中确保你永远不会尝试访问非id列,否则它会尝试获取对象。(这可能会触发致命的查询)。您必须检查父节点是否具有rid(除了instance_id),否则返回null。-我很遗憾地不知道,如果这真的有用,延迟加载的代理对象通常为id列提供可用的getter,但我从未尝试过使用复合键。
1.添加一个parent_instance_id列,这在技术上是冗余的,并会带来自己的问题,但它会工作,因为当不存在父列时,它可以为null。
1.对于这个特定的父关系,根本不使用原则关联Map。相反,将其保留为属性,并在请求时从存储库中获取父关系(通过id)(从技术上讲,这是选项2的降级,但它更容易控制,但关注点分离是肮脏的,因为实体不应该进行查询或从存储库中获取内容)

相关问题