• Hibernate JPA中的N+1 选择问题以及如何避免它


    让我们通过一些例子来谈谈休眠实体中臭名昭着的N + 1选择问题以及如何解决它。您可能还在某些地方听说过N + 1查询问题。

    什么是 N+1 选择问题?

    N+1 选择问题是性能反模式,其中应用程序对数据库进行 N+1 个小查询,而不是 1 个查询获取所有必需的数据。

    让我们以博客文章及其评论为例。在这个系统中,我们有帖子评论实体。每条评论都引用了各自的帖子。这意味着从“发布”到“评论”实体的关系遵循一到多映射

    如果您正在考虑评论以对一种关系发布为多个,那么您也是正确的。

    这两个实体的表值如下所示。

    1. insert into post (id,title,content,author) values(1, 'Some title 1','Some long content 1','John Doe' );
    2. insert into post (id,title,content,author) values(2, 'Some title 2','Some long content 2','John Doe' );
    3. insert into post (id,title,content,author) values(3, 'Some title 3','Some long content 3','John Doe' );
    4. insert into post (id,title,content,author) values(4, 'Some title 4','Some long content 4','John Doe' );
    5. insert into post (id,title,content,author) values(5, 'Some title 5','Some long content 5','John Doe' );
    6. insert into comment (id, content, author, post_id) values (1, 'some comment on post1 ', 'John Doe', 1);
    7. insert into comment (id, content, author, post_id) values (2, 'some comment on post1 ', 'John Doe', 1);
    8. insert into comment (id, content, author, post_id) values (3, 'some comment on post2 ', 'John Doe', 2);
    9. insert into comment (id, content, author, post_id) values (4, 'some comment on post3 ', 'John Doe', 3);
    10. insert into comment (id, content, author, post_id) values (5, 'some comment on post4 ', 'John Doe', 4);
    11. insert into comment (id, content, author, post_id) values (6, 'some comment on post4 ', 'John Doe', 4);
    12. insert into comment (id, content, author, post_id) values (7, 'some comment on post4 ', 'John Doe', 4);
    13. insert into comment (id, content, author, post_id) values (8, 'some comment on post5 ', 'John Doe', 5);

    如您所见,单个帖子包含多个评论,每条评论都指向一个帖子。因此,让我们使用这些实体设置一个弹簧启动项目,并从数据库中查询它们。

    1. public interface CommentRepository extends JpaRepository {
    2. List findAll();
    3. }
    如果您想了解有关jpa存储库的更多信息,请阅读 春季JPA简介

    此外,我还编写了一个命令行运行程序,以便在应用程序启动时触发查询。

    1. @SpringBootApplication
    2. public class SpringBootNPlusOneApplication implements CommandLineRunner {
    3. public static final Logger logger = LoggerFactory.getLogger(SpringBootNPlusOneApplication.class);
    4. @Autowired
    5. CommentRepository commentRepository;
    6. public static void main(String[] args) {
    7. SpringApplication.run(SpringBootNPlusOneApplication.class, args);
    8. }
    9. @Transactional
    10. @Override
    11. public void run(String... args) throws Exception {
    12. logger.info("Finding all comment objects");
    13. List comments = commentRepository.findAll();
    14. for (Comment comment : comments) {
    15. logger.info("Comment [{}] from Post [{}]",comment.getContent(), comment.getPost().getTitle());
    16. }
    17. }
    18. }
    通过所有这些设置,让我们 在Spring boot应用程序中启用SQL日志并运行我们的应用程序。

    如您所见,底层休眠框架正在创建一个查询来加载所有注释。但它也会在单独的查询上加载每个帖子。

    想象一下,你有2000条评论和500个帖子。因此,将有一个查询从数据库中获取2000条评论。但是,还将有500多个查询到数据库,用于获取这些评论引用的每个帖子。在现实世界中,这对数据库来说工作量太大了。此外,这将需要更多的网络往返,这反过来又会增加整体处理时间。这就是我们所说的N+1查询问题或N+1休眠选择问题。

    如何解决N+1查询问题?

    N+1 问题的原因是休眠的急切加载特性。默认情况下,休眠将始终加载@ManyToOne子级(获取类型.EAGER)。因此,如果尚未从数据库加载每个 post 对象,它将加载该对象。

    让我们来看看休眠的N + 1选择问题的可能解决方案以及它们如何适合。

    最初的想法是,您可以懒惰地加载孩子。因此,要执行此操作,请将注释设置为@ManyToOne(提取 = FetchType.LAZY)。在后台,延迟加载会为子对象创建代理对象。 当我们访问子对象时,休眠将触发查询并加载它们。话虽如此,这可能看起来是一个好主意,但延迟加载并不是完美的解决方案

    这是由于延迟加载背后的底层逻辑。启用延迟加载后,休眠将为“Comment.post”字段创建代理。访问 post 字段时,休眠将使用数据库中的值填充代理。是的,对于每个 Post 对象,仍会发生查询。

    我们也可以在日志中看到这种行为。

    正如您在此处看到的,我们仍然看到对 POST 表的查询。但是,当我们循环访问注释对象以访问时,就会发生这种情况。comment.getPost().getTitle()

    解决方案

    那么我们如何解决N+1问题呢?为此,我们需要回到SQL的一些基础知识。当我们必须从两个单独的表加载数据时,我们可以使用联接。因此,我们可以编写如下所示的单个查询,而不是使用多个查询。

    1. select *
    2. from comment c
    3. inner join post p on c.post_id = p.id
    同样,我们可以在春季数据JPA中加入获取结果。为此,您需要添加自定义 @Query注释。以下是更改后的存储库。
    1. public interface CommentRepository extends JpaRepository {
    2. @Query("select c from Comment c join fetch c.post")
    3. List findAll();
    4. }
    正如您在这里看到的,我们使用  join fetch 关键字在同一查询中加载  post 对象。让我们再次运行我们的应用程序并检查结果。

    消除 N+1 选择休眠问题

    如屏幕截图所示,对数据库只有一个查询。它在同一查询中与 post 表进行内部联接。此外,在处理注释时,没有新的查询进入 DB。

    如何找到N+1问题?

    查找 N+1 问题的最简单方法是通过 SQL 日志。在春季启动中显示 SQL 日志并对其进行分析会更容易。因此,在测试代码时,请确保启用跟踪日志以查找此类 N+1 的出现次数。

    查找 N+1 问题的系统方法是开始查看实体映射和存储库查询。每当数据库中有对象列表时,您可能希望检查业务逻辑是否访问了其子实体。如果是,那么您应该选择加入“加入抓取”。

    为什么你应该避免N+1问题?

    原因很简单。

    1. N+1 问题会创建更多对数据库的查询。这意味着数据库将过载。
    2. 对数据库的更多查询会影响数据库和应用程序服务器的性能。每个查询和处理响应都需要更多的 CPU 周期。
    3. 每个额外的查询都会增加所有处理时间。由于每个查询都需要发送和接收查询和结果,因此处理时间会成比例地增加。
    4. 更长的处理时间意味着来自连接池的更多开放连接。这会影响其他请求,因为它们必须等待更长时间才能获得连接。

    总结

    最后,我们了解了休眠中的N+1问题是什么,以及如何使用连接获取来解决它们。如果您想尝试这些示例,可以随时在我们的GitHub帐户中找到代码。

  • 相关阅读:
    复习C部分:三大循环while篇(内含continue(常用场景2)和break(常用场景1)介绍和使用详情)
    idea+SpringBoot使用过程中的问题集合
    单条视频播放近4000w,如何利用“跟风效应”实现流量暴增?
    Codeforces Round #811 (Div. 3)
    Spring Boot 2 (十):Spring Boot 中的响应式编程和 WebFlux 入门
    C++碎片化知识点记录(2)
    为什么虚拟dom会提高性能?
    Leetcode 中等:95. 不同的二叉搜索树II
    【自动驾驶】ROS小车系统
    【AI】如何让两个图案重叠的部分变成透明
  • 原文地址:https://blog.csdn.net/allway2/article/details/127122326