【问题标题】:preventing N+1 selects in JPA [duplicate]防止 JPA 中的 N+1 选择 [重复]
【发布时间】:2012-11-04 05:11:01
【问题描述】:

我的 JPA 实体 Order 与 Customer 具有多对一关系。它是双向的,因此 Customer 也有一个 OneToMany 字段的订单。这两个关系都使用 EAGER 提取(或在 OpenJPA 提取计划中)。

当我从 Order 中选择时,我得到 1 个选择的订单和 N 个选择的 Customer.orders 字段。令我惊讶的是,OpenJPA、EclipseLink 和 Hibernate 都存在这个问题,即使我使用 JOIN FETCH(它在单向情况下确实有效)。

有什么好办法解决这个问题吗? 对于更复杂的图,是否有解决 N+1 选择问题的解决方案?

编辑:我自己的研究结果: - 对于 OpenJPA(我正在使用)我还不知道解决方案 - 对于 Hibernate,@Fetch(FetchMode.SUBSELECT) 解决了这个问题。使用@BatchSize 也有帮助,这可以同时选择给定数量的 customer.orders 字段。 - 对于 EclipseLink,我发现了一个类似的功能 @BatchFetch(value=BatchFetchType.IN) 但在这种情况下它没有帮助,我想它不能有效地处理双向关系。

【问题讨论】:

  • 这是一个有点漫不经心的问题。您有一个具体问题想要帮助解决还是只想投诉 JPA?
  • 你说得对,我对 JPA 有点失望,我将编辑我的问题,使其更切中要害。
  • 您真的需要 EAGER 获取 Customer.orders 吗?
  • @Esteve 你有一个很好的观点。在制作订单及其客户的简单列表时,您实际上并不需要这些客户的任何其他订单!仍然可能存在其他情况,您确实需要加载这样的字段,我认为可以在没有 N+1 选择的情况下选择它

标签: java sql performance jpa


【解决方案1】:

看看:What is SELECT N+1?,那里有很多很好的信息。

如果您使用 Hibernate:Hibernate - Chapter 19: Improving Performance - Fetching Strategies

My own personal solution is to use native SQL and tmp ids table 那是因为一般恕我直言,N+1 选择问题主要是批处理的问题。否则延迟加载(通常是 N+1 解决方案)可能对性能有益。

【讨论】:

  • 谢谢,使用本机查询肯定会有所帮助。但这感觉就像你必须手写一些应该由 ORM 负责执行的东西。这可能会导致大量手动工作和维护噩梦。
  • @SlowStrider 这个想法是你不应该经常这样做。 N+1 延迟加载通常是解决大多数问题的正确方法。此外,当涉及到像这样的性能可靠优化时,您会发现 SQL(不是 JPA HQL)是您唯一的选择……所以除非必须,否则不要这样做。
  • 如果你能告诉我为什么,这对反对的选民会有所帮助。 N+1 选择问题无法神奇地解决。你要么有一个笛卡尔积的字段,要么你懒加载每个对象。我不确定人们在期待什么。没有一个该死的 ORM 可以预测数据库性能并随机决定何时做急切(笛卡尔积)与懒惰(N+1)。我不确定人们在期待什么。很抱歉,我不能只提供一个剪切粘贴的代码来解决这个问题。
  • 我认为反对的原因是您说 N+1 延迟加载通常是正确的方法。我不同意...实际上我认为 JPA 的集合映射通常适得其反。只需触发一个单独的(非本地)查询来获取 customerId = ... 的订单。无论如何,我仍然认为不赞成投票。
【解决方案2】:

这是一个解决方案:

  1. 将实体层与 API 层分开,并仅在您的应用程序内与 API 实例进行交互。这个上下文中的 api 也可以称为 DTO。

  2. 从实体中完全删除关系。

  3. 创建一种机制来指示您希望获取子项。例如:将 fetchRequestList 传播到将 API 映射到实体的层(这样您就可以有条件地获取)。

  4. 在查询执行期间,像往常一样收集父对象。

  5. 使用基于 FK 到父 PK 的带有 IN 子句的命名参数化查询检索整个子代集合。

  6. 遍历结果并将它们与父母相匹配。

这将强制 ORM 执行 n+1 次查询,而不是 n(n+1) 次查询。请记住,您现在必须使用自定义逻辑实现级联保存、删除、更新等。

【讨论】:

    【解决方案3】:

    在任何 ORM 框架中,N+1 问题都很常见。你无法避免这一点。但是,这更多的是关于你采取什么样的方法来解决这个问题。您可以根据您的实现使用关联和延迟加载或急切加载日期。您还可以在单​​个查询中执行数据库映射并获取所有关联数据,然后将其映射到您的模型。由于数据库已建立索引,因此此操作可能比您使用 N+1 查询获取数据和映射更快(如果您的网络延迟允许)。

    【讨论】:

      猜你喜欢
      • 2010-10-09
      • 2012-09-28
      • 1970-01-01
      • 2011-06-17
      • 1970-01-01
      • 1970-01-01
      • 2012-10-01
      • 1970-01-01
      • 2011-08-29
      相关资源
      最近更新 更多