在本文中,我们将使用一个实际示例,其对应用程序性能的影响以及创新的解决方案如何帮助检测和减轻N + 1查询问题。
如果您使用了Hibernate等任何ORM框架,则可以证明在简单的常规类中定义数据模型并将其映射到任何关系数据库是多么容易。使用Hibernate等ORM工具,我们可以使用面向对象的编程语言和关系数据库的大部分便利性。
。尽管编程语言具有不同的ORM工具,但它们的通用工具是相似的:它们会加快开发,保持代码干燥,提高安全性并减少掌握RAW SQL查询的需求。
作为开发人员,您还应该注意任何技术的缺点。臭名昭著的N + 1查询问题是一种反图案,它源于Orms提供的漏水。因为ORMS使访问数据在不进行查询的情况下变得非常简单,因此它们也使访问该数据降低效率低下。此问题主要与ORM框架如何处理相关条目的懒惰加载有关。您可以找到下面的详细信息。
什么是n+1查询问题?
为了更好地理解N + 1查询问题,我们首先需要了解ORM工具中数据获取的概念。默认情况下,大多数ORM工具,例如Hibernate和Django,都将懒负载作为默认数据获取行为。这意味着他们仅从数据库中明确索取数据并延迟您尚未请求的任何相关数据。
让我们看看一个例子。假设您正在建立一个带有多个博客文章的博客应用程序,并且每个博客文章都通过一对多的关系链接到其各自的作者。如果您的应用程序获取博客列表,然后使用单独的查询获取每个博客的相关作者,则会导致数据库的许多往返。
// Fetching All Blogs
List<Blog> blogs = blogRepository.findAll();
// N+1 Queries: One Query for Blogs, N Queries for Authors
for (Blog blog : blogs) {
Author author = blog.getAuthor(); // Triggers a separate query for each blog post's author
}
棘手的部分注意到这正在发生。在您的代码中,您似乎只是在对象上迭代。由于ORMS在SQL上提供了抽象,因此它们也很难猜测哪种对象属性访问会导致查询。在这种情况下,您正在运行一个查询来获取博客文章列表,然后运行多个查询,以获取与每个博客文章相对应的作者的数量,因此n + 1查询问题。
您数据库中的博客文章和作者越多,问题就会越严重,从而影响了应用程序性能和用户体验。这是n + 1查询问题可能引起的一些问题:
-
缓慢的响应时间:n + 1查询问题减慢了数据检索,这使您的应用程序对用户请求的响应较低。
-
有限的可伸缩性:随着应用程序的增长,n + 1查询问题可能会限制您的应用程序处理不断增长的用户需求的能力。
-
增加数据库负载:n + 1查询问题引起的查询数量增加了您的数据库。
-
有限的吞吐量:n + 1查询问题减少了您的数据库可以处理的交易数量。
-
降低用户体验:随着加载时间缓慢,用户可能会等待更长的时间,至少可以说,这是令人沮丧的。
解决n+1查询问题
大多数ORMS还提供了通过控制相关实体的懒惰和急切的加载来避免N+1查询的方法。使用懒惰负载,相关实体被加载为代理,只有在代码中访问了一个实体属性时才能初始化。另一方面,急切的加载在初始化原始实体时执行SQL加入,因此无需进一步查询。
Hibernate提供了几种配置急切加载的方法,无论是在关系映射本身还是每个查询上:
:1.将实体关系配置为渴望
这是三种策略中最具侵略性的,因为它从根本上改变了实体的加载方式。默认情况下,大多数ORM工具(例如Hibernate Lazy Load相关的实体),导致多个数据库查询。急切地指示您的ORM在单个查询中获取主实体及其相关实体时,配置相关实体之间的关系。
@Entity
public class Post {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String title;
private String content;
@ManyToOne(fetch = FetchType.EAGER) // Eager loading
@JoinColumn(name = "author_id")
private Author author;
// Getters and setters
}
仅当您知道实体关系和属性几乎总是访问时,才使用此设置。
2.在映射上设置批处理首选项
在与具有复杂关系的实体打交道时,这可能是n + 1查询问题的一个很好的解决方案。设置批处理偏好使您可以将多个查询划分为一个查询。在我们较早的帖子和作者示例中,这意味着我们可以在单个查询中获取所有帖子及其各自的作者。如果您使用的是Springboot,则可以在Lazy Association上使用@bathsize注释。
@Entity
public class Author {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
@OneToMany(mappedBy = "author", fetch = FetchType.LAZY)
@BatchSize(size = 10) // Batch loading strategy
private List<Post> posts = new ArrayList<>();
// Getters and setters
}
指示您的ORM工具批处理多个查询会在获取相关实体时减少数据库的往返数量,从而有效求解N + 1查询问题。
3.急切的查询级别加载
您还可以通过明确指定应与主实体一起急切地加载的查询来防止N + 1查询问题。当您确切地知道要获取哪些相关实体,消除了随后的查询的需求时,此方法很有用。
public interface PostRepository extends JpaRepository<Post, Long> {
@Query("SELECT p FROM Post p JOIN FETCH p.author")
List<Post> findAllPostsWithAuthorsEagerly();
}
如果您只需要偶尔获取相关实体,并且您的实体之间的关系非常简单,那么在查询级别上急切的加载是过分的。您可以选择使用手动加载或依靠ORM的默认急切加载。
4.代码中的与负载相关的实体
这种方法可在从数据库中加载相关实体时为您提供颗粒状控制。 ORM框架通常提供允许您加载相关实体的方法。例如,如果您的应用程序具有与作者有关的邮政实体,则可以使用诸如post.getauthor()之类的方法手动加载相关的作者。您避免使用这种手动方法遇到N + 1查询问题。
@Service
public class PostService {
@Autowired
private PostRepository postRepository;
public Post getPostWithAuthor(Long postId) {
Post post = postRepository.findById(postId).orElse(null);
if (post != null) {
// Manually load the author for every blog post
Hibernate.initialize(post.getAuthor());
}
return post;
}
}
了解持续的反馈
DIGMA连续反馈是一个运行时衬里,可以使开发人员快速识别复杂代码库中的风险代码,潜在错误和瓶颈。它在幕后使用opentelemetry来收集当地运行代码时的痕迹,日志和指标等数据。
一旦收集了数据,Digma就会寻找回归,异常,代码气味或其他模式,这些模式对于在开发阶段时可能了解您的代码很有用。这使您可以加速开发周期,同时捕获代码中的潜在问题。
连续反馈如何帮助检测N+1查询问题
随着您的应用程序的增长,此类查询问题可能会成为严重的性能瓶颈,从而大大增加数据库负载并减慢应用程序性能
。如果您有一个复杂的应用程序对数据库进行数百个查询,则只需阅读代码就很难发现它们。但是,使用Digma,即使使用复杂的代码库,您也可以轻松地检测到此类查询。
与n+1相关的重复查询之类的问题易于检测到运行时数据中。具体而言,在Digma自动收集使用OpenTelemetry的轨迹中。
这是n+1选择查询的样子:
DIGMA收集并分析了数据后,它突显了负责获取用户角色的第二个查询具有潜在的n + 1查询问题。然后Digma在查询源代码/
旁边生成一个有见地的警报除此之外,您还可以注意到,Digma还突出了对响应时间的影响,表明处理此查询的端点(例如,Get /所有者)受N+1问题的影响。虽然10个重复DB调用的查询时间为1.1 ms的持续时间似乎很小,但如果您的应用程序提出更多查询,这会很快增加,从而损害您的应用程序的性能。
digma还走得更远,并在端点级别提供见解。这使您可以注意到可能受N + 1查询问题影响的终点,影响的严重程度以及引起问题的方法,如下输出所示。
。在上面的Digma Insight上武装,在查询和端点级别上,您可以轻松找到并修复导致n + 1查询问题的代码。
案例研究:减少特定API的执行时间
马库斯·韦斯特格伦(Markus Westergren)是一位经验丰富的高级顾问/员工工程师,在信息技术和服务行业工作的历史记录。 Markus是Digma的Beta用户,最近与我们分享了他们可以在代码中检测到此类n+1查询问题,从而减少了特定API的执行时间。
您遇到了哪些编码问题,使您尝试了Digma?
我最近加入了一个正在开发新产品的团队。某些用例存在性能问题。我的工作是找到并修复它们。我使用手动分析和剖面师尝试在代码中查找热点。我所做的任何更改都必须经过测试和测量才能查看是否解决了问题。
Digma如何帮助您解决这些问题?
有了Digma,我会收到有关应用程序性能的持续反馈。它会立即向我展示最大的瓶颈在哪里,所以我不必自己寻找它们。现在,我可以收到有关我的最新修复程序与以前版本相比的表现的直接反馈。它还发现了我们甚至不知道的瓶颈。它从图片中取出了猜测。我们节省了很多时间查找和解决绩效问题。
DIGMA如何帮助处理SQL查询中的可疑N-Plus-1?
我们正在使用Hibernate,这使您更容易看到真正发生的事情。
我们已经确定了在某些情况下产生影响应用程序性能的N-Plus-1查询。
我们做出了很大的改进,Digma帮助我们确定了在某些情况下n+1查询可能会影响应用程序性能的情况。
除了重复数据库跨度的洞察力外,Digma还提供了关键端点见解,使我们能够检测和修复与特定端点相关的n + 1查询,以及其他性能问题。
digma当然是我们开发工作流的一个很好的补充,我们将继续利用它来优化和改进我们的代码并提供最佳的用户体验。
结论
在使用Hibernate等ORM框架时,N + 1查询问题是一个常见的问题。尽管不同的解决方案(例如由ORM框架提供的懒惰加载)可以帮助避免此问题,但检测此类查询至关重要。 Digma是一种可靠的工具,可以为您节省查找此类查询的手动努力。使用Digma在开发工作流程中,您可以利用连续的证据反馈来优化您的代码。
快乐观察:)