【问题标题】:Is this an N+1 problem and how do I fix it?这是一个 N+1 问题,我该如何解决?
【发布时间】:2016-04-12 09:36:40
【问题描述】:

我有一份项目清单和一份客户清单。一个项目可以针对一个客户,每个客户可以有多个项目。所以这是一个简单的 1:n 关系,项目是拥有方。

简化到本质

@Entity
public class Project {
  @Id
  long id;

  @ManyToOne(optional = true)
  @JoinColumn(name = "customer", nullable = true, updatable = true)
  Customer customer;
}

@Entity
public class Customer {
  @Id
  long id;
}

当我加载项目列表时,我想同时有效地检索客户。不是这种情况。项目只有一个查询,然后针对遇到的每个不同客户发出一个单独的查询。

假设我有 100 个项目分配给 50 个不同的客户。这将导致对项目进行一次查询,对客户进行 50 次查询。

这很快就会增加,对于大型项目/客户列表,我们的应用程序变得相当慢。这也只是一个例子。我们所有有关系的实体都会受到这种行为的影响。

我已经按照 here 的建议在 customers 字段上尝试了 @Fetch(FetchMode.JOIN),但它什么也没做,并且根据 Hibernate,FetchMode.SUBQUERY 不适用:

org.hibernate.AnnotationException:在 ToOne 关联中不允许使用 FetchMode.SUBSELECT

我该如何解决这个问题?

【问题讨论】:

  • 我不认为这是一个 N+1 问题,您的 @ManyToOne 关系的默认 fetch-type 已经是 FetchType.EAGER

标签: java hibernate jpa query-performance


【解决方案1】:

如果您使用Spring Data JPA 来实现您的存储库,您可以在JPA 实体中指定延迟获取:

@Entity
public class Project {
  @Id
  long id;

  @ManyToOne(fetch = FetchType.LAZY, optional = true)
  @JoinColumn(name = "customer", nullable = true, updatable = true)
  Customer customer;
}

@Entity
public class Customer {
  @Id
  long id;
...
}

并将@EntityGraph 添加到基于Spring Data JPA 的存储库中:

@Repository
public interface ProjectDao extends JpaRepository<Project, Long> {

    @EntityGraph(
            type = EntityGraphType.FETCH,
            attributePaths = { 
                    "customer" 
            }
    )
    Optional<Project> findById(Long id);
...
}

我在https://tech.asimio.net/2020/11/06/Preventing-N-plus-1-select-problem-using-Spring-Data-JPA-EntityGraph.html 的博文帮助您防止使用Spring Data JPA 和@EntityGraph 的N+1 选择问题。

【讨论】:

  • 谢谢楼主分享!这对我有用:)
【解决方案2】:

是的,这是一个 n+1 选择问题的书本示例。

我在大多数情况下使用的方法是使关联变得惰性并定义batch size。

或者,您可以使用带有 [left] join fetch 的 JPQL 查询直接从查询结果集中初始化关联:

select p from Project p left join fetch p.customer

【讨论】:

  • 这是唯一的方法吗?我问是因为我使用 Spring Data 并且并不总是有书面查询。 @Fetch(FetchMode.JOIN) 不应该这样做吗?
  • FetchMode 对查询没有影响(Spring Data 可能会根据存储库方法中的命名约定在后台自动生成)。 PS Spring Data 不会阻止您编写查询。
  • 没错,但我使用的是JpaSpecificationExecutor 接口,afaik 不允许编写查询。 I can set the fetch mode in a Specification 但这对我来说看起来超级 hacky。我基本上必须依靠规范的副作用来实现我想要的。
  • 我的回答考虑了纯 JPA/Hibernate 行为,您必须查阅您使用的任何包装器的文档。但是不管是哪一个,都只能委托给底层的ORM,所以基本概念是一样的。恕我直言,如果您使用不允许您编写自定义 JPA 查询的 ORM 包装器,那么它非常不灵活且受到限制。
  • 是的。但即便如此,有时我对一个实体有许多不同的查询,并且必须将许多连接提取冗余地添加到许多查询中。而且我什至不能加入获取传递关系。如果这真的是唯一的方法,那么 JPA 本身就有点局限了。
【解决方案3】:

是的,正如@dragan-bozanovic 所说,这是一个 n+1 选择问题的书本示例。

在 Spring-Boot 2.1.3 中@Fetch(FetchMode.JOIN) 可以解决:

  @ManyToOne(optional = true)
  @Fetch(FetchMode.JOIN)
  @JoinColumn(name = "customer", nullable = true, updatable = true)
  Customer customer;

警告:如果关系可能无效,例如标记为@NotFound(action = NotFoundAction.IGNORE),则每个无效关系都会触发另一个SELECT 查询。

【讨论】:

    猜你喜欢
    • 2022-06-13
    • 2020-03-25
    • 2020-02-14
    • 2021-10-23
    • 1970-01-01
    • 2021-07-31
    相关资源
    最近更新 更多